본문 바로가기
React

[React] MySQL, redux 를 이용하여 게시판 만들기 - 떽떽대는 개발공부

by 떽이 2021. 2. 1.

오늘은 지금까지 react 에서 공부했던 redux, mysql 를 이용하여 이전 게시물에서 구현할 것이다.

이전에 redux 를 이용하여 구현했던 예제 그대로 사용할 것이다.

2021/01/31 - [React] - [React] DB에서 특정 데이터 받아오기 - 떽떽대는 개발공부

 

[React] DB에서 특정 데이터 받아오기 - 떽떽대는 개발공부

2021/01/30 - [React] - [React] DB에서 받아온 데이터 리스트 만들기 - 떽떽대는 개발공부 [React] DB에서 받아온 데이터 리스트 만들기 - 떽떽대는 개발공부 2021/01/29 - [React] - [React/MySQL] react 에서 M..

ddeck.tistory.com

 

 

 

이전 글에서 MySQL 을 이용하여 글 목록 가져오는 것, 특정 글을 클릭했을 때 해당 글에 대한 데이터를 받아오는 것까지 포스팅 했었다.

그러나 치명적인 문제가 발생했는데, api 호출하여 특정 데이터를 받아오는 데에 시간이 너무 오래 걸린다는 것이다.

그래서 나는 글 목록 페이지에 들어왔을 때 DB에 있는 데이터를 redux-store 에 미리 초기화 시킨 후 특정 글 클릭 시 store 에서 데이터를 빼오도록 변경하였다.

src/components/BoardList.js

import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { init_Data, init_lastIdx, selectRow } from '@modules/boardReducer';
import axios from 'axios';

function BoardList() {
	// db에서 받아올 데이터를 담을 그릇
    const [initData, setInitData] = useState([{
        inputData: {
                idx: '',
                title: '',
                content: '',
                writer: '',
                write_date: ''
        },
    }])
	
    // 원활한 데이터 관리를 위해 글 갯수를 파악한다.
    const [initLastIdx, setInitLastIdx] = useState(0)

    useEffect(async() => {
        try{
            const res = await axios.get('/api/BoardList')
            const _inputData = await res.data.map((rowData) => (
            	// 마지막 rowData의 idx 가 lastIdx 가 될 것이다.
                setInitLastIdx(rowData.idx),
                {
                    idx: rowData.idx,
                    title: rowData.title,
                    content: rowData.content,
                    writer: rowData.writer,
                    write_date: rowData.write_date
                })
            )
            // initData 그릇에 concat 으로 추가해준다.
            setInitData(initData.concat(_inputData))
        } catch(e){
            console.error(e.message)
        }
    },[])

	// initData 가 set 될 때마다 boardReducer 의 init_Data와 init_Idx 함수가 호출된다.
    useEffect(() => {
        dispatch(init_Data(initData))
        dispatch(init_lastIdx(initLastIdx))
    }, [initData])
      
//////////////////////////////////////////////////////////////////////////
	store 에 데이터 init 이후
//////////////////////////////////////////////////////////////////////////
    // store 에 저장된 데이터 받아오기
    const {inputData} = useSelector(state => state.boardReducer)
    const {lastIdx} = useSelector(state => state.boardReducer)

	// 함수형 컴포넌트에서 useDispatch 를 사용하기 위해 선언
    const dispatch = useDispatch();
    // 특정 글 클릭되면 boardReducer의 selectRow에 dispatch
    const selectContent = (idx) => {
        dispatch(selectRow(idx))
    }

    return(
        <div>
            <h2>게시판</h2>
            <div>
                <table className='listTable'>
                    <tbody>
                        <tr>
                            <td className='listTableIndex th'>index</td>
                            <td className='listTableTitle th'>title</td>
                        </tr>
                        {lastIdx !== 0 ?
                            inputData.map(rowData => (
                                rowData.idx !== '' && rowData.idx !== undefined && 
                                <tr>
                                    <td className='listTableIndex' onClick={() => {selectContent(rowData.idx)}}>
                                        <Link to={`/BoardContent/${rowData.idx}`}>{rowData.idx}</Link>
                                    </td>
                                    <td className='listTableTitle' onClick={() => {selectContent(rowData.idx)}}>
                                        <Link to={`/BoardContent/${rowData.idx}`}>{rowData.title}</Link>
                                    </td>
                                </tr>
                            )) : 
                            <tr>
                                <td className='listTableIndex'></td>
                                <td className='listTableTitle noData'>작성된 글이 없습니다.</td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        </div>
    )
}

export default BoardList;

 

위와 같이 먼저 BoardList 페이지가 렌더 되면 axios를 톹해 '/api/BoardList'의 api 로 db 요청을 하게 된다.

이제 db 요청을 받을 페이지를 수정한다.

server/routes/index.js

const express = require('express');
const router = express();
const db = require('../config/db')

// http://localhost:4000/ 으로 접속 시 응답메시지 출력
router.get('/BoardList', (req,res) => {
    const sql = 'SELECT idx, title, content, writer, write_date FROM table1';
    db.query(sql, (err, data) => {
        if(!err){ 
            res.send(data); 
        } else { 
            res.send(err); 
        }
    })
})

module.exports = router;

 

클라이언트에서 요청한 호출을 proxy 를 이용하여 서버단에서 받아왔다.

proxy 를 이용하는 법은 전에 포스팅 해 두었다.

이렇게 하면 아래와 같이 리스트 구현이 완료 된다.

 

 

이제 db의 값을 store 에도 저장해 두었기 때문에 특정 글 클릭 시 페이지 이동 후 보여질 페이지는 store 에서 받아올 것이다.

 

이제 글 상세보기 페이지에서 보자.

src/components/BoardContent.js

import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux'
import axios from 'axios';

function BoardContent({match}) {
	// store 에 저장된 데이터를 받아온다.
    const { selectRowData } = useSelector(state => state.boardReducer)
	// 받아온 데이터를 각각 관리
    const [idx, setIdx] = useState(selectRowData.idx)
    const [title, setTitle] = useState(selectRowData.title)
    const [content, setContent] = useState(selectRowData.content)
    const [writer, setWriter] = useState(selectRowData.writer)
    const [date, setDate] = useState(selectRowData.write_date)

    const handleTitle = (e) => {
        setTitle(e.target.value)
    }

    const handleContent = (e) => {
        setContent(e.target.value)
    }

    const handleWriter =(e) => {
        setWriter(e.target.value)
    }
	
    // date 가 호출될 때마다 함수 실행
    // date 뒤에 시간을 지울 것이다.
    useEffect(() => {
        setDate(date.split('T')[0])
    }, [date])

    const onChange = async() => {
    	// edit 버튼 클릭 시 ghcnf
        try{
        	// axios.post 는 config 로 받아갈 값이 3번째의 매개변수이기 때문에 data 를 임의로 넣어주었다.
            const res = await axios.post('/api/BoardUpdate', {'data':'data'}, {
                params: {
                    'idx': idx,
                    'title': title,
                    'content': content,
                    'writer': writer,
                    'write_date': date
                }
            })
            // DB로 관리할 것이기 때문에 useHistory 말고 document.location.href 로 페이지 이동(새로고침 가능)
            document.location.href='/'
        } catch(e) {
            console.error(e.message)
        }
    }

    const onRemove = async() => {
    	// delete 버튼 를릭 시 실행
        try{
        	// axios.get 은 두번째 매개변수로 config 전달
            const res = await axios.get('/api/BoardDelete', {
                params: {
                    'idx': idx
                }
            })
            console.log('delete :: result :: ',res)
            document.location.href='/'
        } catch(e) {
            console.error(e.message)
        }
    }

    return(
        <div>
            <h2>상세보기</h2>
            <div>
                <div>
                    <label for='title'>제목</label>
                    <input type='text' name='title' className='inputTitle' value={title} onChange={handleTitle} />
                </div>
                <div>
                    <label for='writer'>작성자</label>
                    <input type='text' name='title' className='inputWriter' value={writer} onChange={handleWriter} />
                </div>
                <div>
                    <label for='writer'>작성일</label>
                    <input type='text' name='title' className='inputWriter' value={date} disabled />
                </div>
                <div>
                    <label className='_content' for='content'>내용</label>
                    <textarea name='content' className='inputContent' value={content} onChange={handleContent} />
                </div>
                <div>
                    <button type='button' className='editBtn' onClick={onChange}>edit</button>
                    <button type='button' className='deleteBtn' onClick={onRemove}>delete</button>
                </div>
            </div>
        </div>
    )
}

export default BoardContent;

 

호출을 받은 서버에도 코드를 수정해준다.

server.routes/index.js

const express = require('express');
const router = express();
const db = require('../config/db')

// http://localhost:4000/ 으로 접속 시 응답메시지 출력
router.get('/BoardList', (req,res) => {
    const sql = 'SELECT idx, title, content, writer, write_date FROM table1';
    db.query(sql, (err, data) => {
        if(!err){ 
            res.send(data); 
        } else { 
            res.send(err); 
        }
    })
    //res.send({ test : "this is test!!"});
})

// store 로 db 관리를 함으로 호출할 필요 없다.
//router.get('/BoardContent', (req,res) => {
//    const sql = 'SELECT idx, title, content, writer, write_date FROM `table1` WHERE `idx` = ?';
//    const params = req.query.idx
//    db.query(sql, params, (err, data) => {
//        if(!err) {
//            res.send(data)
//        } else {
//            res.send(err)
//        }
//    })
//})

// log 찍기 위해 사용..
const util = require('util')

router.post('/BoardUpdate', (req, res) => {
    const sql = 'UPDATE `table1` SET `title` = ?, `content` = ?, `writer` = ?, `write_date` = ? WHERE `idx` = ?';
    const params = [req.query.title, req.query.content, req.query.writer, req.query.write_date, req.query.idx]
    console.log(`= = =>req ${util.inspect(req.query)}`)
    db.query(sql, params, (err, data) => {
        if(!err) {
            res.send(data)
        } else {
            res.send(err)
        }
    })
})

router.get('/BoardDelete', (req,res) => {
    const sql = 'DELETE FROM `table1` WHERE `idx` = ?';
    const params = req.query.idx
    db.query(sql, params, (err, data) => {
        if(!err) {
            res.send(data)
        } else {
            res.send(err)
        }
    })
})

module.exports = router;

 

이렇게 하면 글 상세보기 페이지에서 수정, 삭제가 가능하다.

이제 글 추가만 더 해보자.

src/components/BoardNew.js

import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import axios from 'axios';

function BoardNew() {
	// 현재 날짜 받아오기
    const today = new Date().toISOString().substr(0,10).replace('T', ' ')
    const [title, setTitle] = useState('')
    const [writer, setWriter] = useState('')
    const [content, setContent] = useState('')
    // store 에서 마지막 idx 받아온다
    const {lastIdx} = useSelector(state => state.boardReducer)

    const handleTitle = (e) => {
        setTitle(e.target.value)
    }

    const handleWriter = (e) => {
        setWriter(e.target.value)
    }

    const handleContent = (e) => {
        setContent(e.target.value)
    }

    const onSave = async() => {
        try{
            const res = await axios.post('/api/BoardInsert', {'data':'data'}, {
                params: {
                	// store 에서 받아온 마지막 idx에서 1을 추가
                    'idx': lastIdx+1,
                    'title': title,
                    'content': content,
                    'writer': writer,
                    'write_date': today
                }
            })
            document.location.href='/'
        } catch(e) {
            console.error(e.message)
        }
    }

    return(
        <div>
            <h2>글 작성</h2>
            <div>
                <div>
                    <input type='text' className='inputTitle' placeholder='제목을 입력하세요' value={title} onChange={handleTitle} />
                </div>
                <div>
                    <input type='text' className='inputTitle' placeholder='작성자를 입력하세요' value={writer} onChange={handleWriter} />
                </div>
                <div>
                    <textarea className='inputContent' placeholder='내용을 입력하세요' value={content} onChange={handleContent} />
                </div>
                <div>
                    <button type='button' onClick={onSave}>submit</button>
                </div>
            </div>
        </div>
    )
}

export default BoardNew;

 

이제 이로써 게시판 예제를 가지고 redux, MySQL 을 이용한 방법을 모두 해 보았다.

다음엔 어떤걸로 포스팅 할 지 조금 더 생각 해보자.

 

 

댓글