React 수업 정리

Board 만들어보기

shchan 2024. 9. 19. 15:07

게시글이 추가, 수정 , 삭제가 가능한 게시판을 구현해보기.

 

- Home에 게시글 List 를 만들고 게시글을 누르면 세부내용이 나오도록 만들어서 수정, 삭제가 가능하도록 구현.

- Home에서 글쓰기 버튼을 누르면 게시글이 추가 될 수 있도록 글쓰는 페이지로 이동하여 내용을 기입하고 등록을 누르면 추가되도록 구현.

- DB랑 연결해서 DB에 데이터를 추가/삭제 수정이 가능하도록 함.

 

npm i express ⇒ 설치 : 서버

npm i mysql ⇒ 설치 : DB

npm i axios ⇒ 설치 : 비동기

npm i cors ⇒ 설치 : 서버와 클라이언트 간의 자원공유 관리

npm i json ⇒ 설치 : json

npm i nodemon ⇒ 설치 : 자동감지 서버 재시작 도구 (소스코드의 변경이 발생하면 자동으로 서버 재시작)

npm i react-router-dom ⇒ react 표준 라우팅 라이브러리 (컴포넌트간의 전환이 일어날때 화면 전환)

 

package.json 에   "proxy" : "http://localhost:5000/" 를 추가 설정 해주어야함.

 

전부 설치후 src폴더안에 server.js  

const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const cors = require('cors');

//express 사용하기 위한 app 생성
const app = express();

//express 사용할 서버포트 설정
const PORT = 5000;

app.use(cors());
app.use(bodyParser.json());

//DB 접속
const db = mysql.createConnection({
    host : 'localhost',
    user: 'react',
    password: 'mysql',
    port:'3306',
    database:'db_react'
});

// express 접속
app.listen(PORT, ()=>{
    console.log(`server connecting on : http://localhost:${PORT}`);
});

//db 연결
db.connect((err)=>{
    if(!err){
        console.log("seccuss");

    }else{
        console.log("fail");
    }
});


// --------- DB에서 값을 가져오기 -------------

// / => root 연결시 보여지는 기본화면 설정
app.get('/', (req, res) =>{
    res.send('React Server Connect Success!!');
});

 

 

DB 데이터 추가 구문 -- 

 

# root 계정으로 실행
create database db_react;
create user 'react'@'localhost' identified by 'mysql';
grant all privileges on db_react.* to react@localhost;
ALTER USER 'react'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql';
FLUSH PRIVILEGES;

 

# react 계정으로 실행
use react;
drop table board;
create table board (
id bigint auto_increment primary key,
title varchar(30),
contents varchar(500),
writer varchar(20),
reg_date timestamp DEFAULT now()
);

 

insert into board(title, contents, writer) values('한 번 배워서 어디서나 사용하기','기술 스택의 나머지 부분에는 관여하지 않기 때문에, 기존 코드를 다시 작성하지 않고도 React의 새로운 기능을 이용해 개발할 수 있습니다.','red');
insert into board(title, contents, writer) values('상태를 가지는 컴포넌트','컴포넌트는 this.props를 이용해 입력 데이터를 다루는 것 외에도 내부적인 상태 데이터를 가질 수 있습니다. 이는 this.state로 접근할 수 있습니다.','orange');
insert into board(title, contents, writer) values('애플리케이션','props와 state를 사용해서 간단한 Todo 애플리케이션을 만들 수 있습니다.','yellow');
insert into board(title, contents, writer) values('외부 플러그인을 사용하는 컴포넌트','React는 유연하며 다른 라이브러리나 프레임워크를 함께 활용할 수 있습니다. 이 예제에서는 외부 마크다운 라이브러리인 remarkable을 사용해 <textarea>의 값을 실시간으로 변환합니다.','green');
insert into board(title, contents, writer) values('자습서를 시작하기 전에','우리는 이 자습서에서 작은 게임을 만들겁니다. 게임을 만들고 싶지 않아서 자습서를 건너뛰고 싶을 수 있습니다. 그래도 한번 해보세요!','blue');
insert into board(title, contents, writer) values('브라우저에 코드 작성하기','먼저 새 탭에서 초기 코드를 열어주세요. 새 탭은 비어있는 틱택토 게임판과 React 코드를 보여줄 것입니다. 우리는 자습서에서 React 코드를 편집할 것입니다.','navy');
insert into board(title, contents, writer) values('React란 무엇인가요?','React는 몇 가지 종류의 컴포넌트를 가지지만 우리는 React.Component의 하위 클래스를 사용해보겠습니다.','purple');

 


 

필요 컴포넌트

BoardHome, BoardList, BoardCreate, BoardDetail, BoardModify

 

 

BoardHome 컴포넌트

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import BoardList from './BoardList';
import BoardDetail from './BoardDetail';
import BoardCreate from './BoardCreate';
import './board.css';
import BoardModify from './BoardModify';

const BoardHome = () => {
    return (
        <div className='boardHome'>
            <h1>MY First Recat Board Page!!!</h1>
            <hr />
            <BrowserRouter>
            <Routes>
            <Route path="/" element= {<BoardList/>} />
            <Route path="/boardList" element= {<BoardList/>} />
            <Route path="/boardDetail/:id" element= {<BoardDetail/>} />
            <Route path="/boardCreate/" element= {<BoardCreate/>} />
            <Route path="/boardModify/:id" element= {<BoardModify/>} />
            
            </Routes>
            </BrowserRouter>

        </div>
    );
};

export default BoardHome;

 

 


 

BoardList 컴포넌트

import React from 'react';
// import { boardList } from '../data/data';
import { Link } from 'react-router-dom';
import './board.css';
import { useState } from 'react';
import axios from 'axios';
import { useEffect } from 'react';


const BoardList = () => {

    // db에 저장되어 있는 board 요소를 가져오기 => boardList 저장

    const [ boardList , setBoardList] = useState([]);


    // 비동기로 db에 접속하여 select로 가져오기
    // get : 데이터 가져올때 (생략 가능.)
    // post : 데이터를 보낼때 (생략 불가능 반드시 써야 함.)
    const getBoardData = async () => {
        const boards = await axios.get('/boardlist');
        console.log(boards);
        setBoardList(boards.data)
    }


    // 컴포넌트가 랜더링 될 때, 혹은 업데이트 될 때 실행되는 hooks 
    /*
        useEffect(() => {
        function    },[deps]);
        - function : 실행시킬 함수
        - deps : 배열형태로 배열안에서 검사하고자 하는 특정값 
    */
    useEffect(() => {
        getBoardData();
    },[]);

    // 서버에서 데이터를 가져오는 것보다 화면에서 랜더링 되는 속도가 더 빠름
    // 조건을 걸어줘서 error 방지

    if(boardList.length > 0 ){
    return (
        <div className='boardList'>
            <h2> Board List Page </h2>
            <table>
                <thead>
                    <tr>
                        <th>번호</th>
                        <th>제목</th>
                        <th>작성자</th>
                        <th>작성일</th>
                    </tr>
                </thead>
                <tbody>

                {
                     boardList.map(b => (
                        

                    <tr key={b.id}>
                    <td>{b.id}</td>
                    <td><Link to={`/boardDetail/${b.id}`}>{b.title}</Link></td>
                    <td>{b.writer}</td>
                    <td>{b.reg_date.substring(0,10)}</td>   
                    </tr>

                        ))
                    }
                </tbody>
            </table>
            <Link to={'/boardCreate'}><button>글쓰기</button></Link>
        </div>
    );
    };
};

export default BoardList;

 

리스트에 보여질 게시글 목록 데이터를 가져오기위해 server.js에 추가해야하는 구문

// 게시글 목록 가져오기
app.get('/boardlist', (req, res) => {
    console.log('/boardlist');
    const sql = 'select * from board order by id desc';
    db.query(sql, (err, data) => {
        if(!err){
            res.send(data);
        } else {
            console.log(err);
            res.send('전송오류');
        }
    })
});

 


 

BoardDetail 컴포넌트

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import './board.css';
import { Link } from 'react-router-dom';
import axios from 'axios';

const BoardDetail = () => {

    const { id } = useParams();

    const [ boardDetail , setBoardDetail ] = useState(null);

    const getDetailData = async () => {
        try{
            const board = await axios.get(`/boardDetail/${id}`);
            console.log(board);
            setBoardDetail(board.data[0])
        } catch(error){
            console.log(error);
        }
    }

    useEffect(() => {
        getDetailData();
    },[]);


    const boardDelete = async () => {
        if(window.confirm('삭제하시겠습니까?')){
            try{
                const res = await axios.post(`/boardDetail/${id}` , boardDetail)
                console.log(res);
                window.location.href = "/boardlist";
            }catch(error){
                console.log(error)
            }
        }
    }


    // const boardItem = boardDetail.find(b => b.id === parseInt(id));

    if(boardDetail != null){
        return (
            <div className='boardDetail'>

                <h2> Board Detail Page </h2>
                
                <table>
                    <tbody>
                        <tr>
                            <th>번호</th>
                            <td>{boardDetail.id}</td>
                        </tr>
                        <tr>
                            <th>제목</th>
                            <td>{boardDetail.title}</td>
                        </tr>
                        <tr>
                            <th>작성자</th>
                            <td>{boardDetail.writer}</td>
                        </tr>
                        <tr>
                            <th>작성일</th>
                            <td>{boardDetail.reg_date.substring(0,10)}</td>
                        </tr>
                        <tr>
                            <th>내용</th>
                            <td>{boardDetail.contents}</td>
                        </tr>
                    </tbody>
                </table>

                <div className='buttons'>
                <Link to={'/boardList'}><button>메인</button></Link>
                <Link to={`/boardModify/${id}`}><button>수정</button></Link>
                <button onClick={boardDelete}>삭제</button>
            
                </div>
            </div>
        
        );
    };

};

export default BoardDetail;

 

게시글 하나를 가져오기위해 server.js에 추가해야하는구문

// 게시글 하나 가져오기 : id
// 화면에서 서버로 요청하는 값 : request (req)
// 서버에서 화면으로 보내주는 값 : response (res)
// 화면에서 가져온 파라미터 추출 : req.params.id 

app.get('/boardDetail/:id',(req,res) => {
    const sql = `select * from board where id = ${req.params.id}`;
    db.query(sql, (err, data) => {
        if(!err){
            res.send(data);
        } else {
            console.log(err);
            res.send('전송오류');
        }
    })
});

 

그냥 게시글 전체를 가져와서 id를 비교해서 맞는거만 띄우면 되는거아니냐는 의문이 생김.

=> 데이터의 수가 지금처럼 적지않고 많다면 전체를 다 가져오는 것은 비효율적,

 DB를 사용하기위한 이용료를 지불하는 입장에서  생각하면 데이터가 쓸데없이 많아지는 것은 좋지않음.

 

게시글 하나를 삭제하기위해 server.js에 추가해야하는 구문

app.post('/boardDetail/:id', (req, res) => {

    const sql = `delete from board where id = ${req.params.id}`;

    db.query(sql, (err, data) => {
        if(!err){
            res.sendStatus(200); // 전송잘됨.
        } else {
            console.log(err);
            res.send('전송오류');
        }
    })
})

BoardCreate 컴포넌트

import React, { useState } from 'react';
import './board.css';
import axios from 'axios';

const BoardCreate = () => {

    const [board, setBoard] = useState({
        title: '',
        writer: '',
        contents: '',
    });

    const onChange = (e) => {
        const { name, value } = e.target;
        setBoard({
            ...board,
            [name]: value
        });
    };


    const onReset = () => {
        setBoard({
            title: '',
            writer: '',
            contents: ''
        });
    }

    const boardAdd = async () => {
        // board 객체를 서버로 전송
        // board 객체의 내용중 하나라도 null이면 안됨.
        if(board.title === ''){
            alert('title is null!!');
            return;
        }
        if(board.writer === ''){
            alert('writer is null!!');
            return;
        }
        if(board.contents === ''){
            alert('contents is null!!');
            return;
        }
        if(window.confirm('등록하시겠습니까?')){
            try{
                const res = await axios.post('/create', board);
                console.log(res);
                // if(res.data === 'ok){
                // }
                // 데이터 전송 후 이동
                window.location.href = "/boardlist";
            }catch(error){
                console.log(error);
            }
        }
    }



    
    return (
        <div className='boardCreate'>
               <h2> Board Create Page </h2>

                <div >
                제목<input className='titleInput' type="text" name='title' placeholder='제목을 입력해주세요' onChange={onChange} value={board.title}/>
                이름<input className='nameInput' type="text" name='writer' placeholder='작성자' onChange={onChange} value={board.writer}/>
                </div>
                <textarea type="text" name='contents' placeholder='내용을 입력해주세요' onChange={onChange} value={board.contents}/>
                
              <button onClick={onReset}>리셋</button>
              <button onClick={boardAdd}>등록</button>
        </div>
    );
};




export default BoardCreate;

 

board 등록하기위해 server.js 에 추가해야하는 구문

// board 등록

app.post('/create', (req, res) => {
    // 파라미터 가져오기 request.body
    
    // const board = req.body;
    // board.title
    const { title, writer, contents } = req.body;

    const sql = `insert into board(title, writer, contents) value (?,?,?)`;

    db.query(sql, [title,writer,contents], (err, data) => {
        if(!err){
            // res.send(data);
            res.sendStatus(200); // 전송잘됨.
        } else {
            console.log(err);
            res.send('전송오류');
        }
    })

})

 


boardModify 컴포넌트

import React, { useEffect, useState } from 'react';
import './board.css';
import { useParams } from 'react-router-dom';
import axios from 'axios';

const BoardModify = () => {

    const { id } = useParams();

    const [ boardModify , setBoardModify ] = useState(null);
    
    const [formData, setFormData] = useState({
        title: '',
        contents: ''
    });

    const getModifyData = async () => {
        try{
            const board = await axios.get(`/boardDetail/${id}`);
            console.log(board);
            setBoardModify(board.data[0]);
            setFormData({
                title: board.data[0].title,
                contents: board.data[0].contents,
                writer: board.data[0].writer,
            });
        } catch(error){
            console.log(error);
        }
    }

    useEffect(() => {
        getModifyData();
    },[]);


    const onChange = (e) => {
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value,
        });
    };

    const onModify = async () => {

        if(formData.title === ''){
            alert('title is null!!');
            return;
        }
        if(formData.writer === ''){
            alert('writer is null!!');
            return;
        }
        if(formData.contents === ''){
            alert('contents is null!!');
            return;
        }
        if(window.confirm('수정하시겠습니까?')){
            try{
                const res = await axios.post(`/modify/${id}`, formData);
                console.log(res);
                // if(res.data === 'ok){
                // }
                // 데이터 전송 후 이동
                window.location.href = "/boardlist";
            }catch(error){
                console.log(error);
            }
        }
    }


    if(boardModify !== null){
    return (
        <div className='boardModify'>
              <h2> {boardModify.id} / Board Modify Page </h2>

              <div>
                제목
                    <input
                        type="text"
                        name="title"
                        placeholder="제목"
                        value={formData.title}
                        onChange={onChange}
                        className='titleInput'
                    />
                작성자
                    <input
                        type="text"
                        name="writer"
                        placeholder="작성자"
                        value={formData.writer}
                        readOnly
                        onChange={onChange}
                        className='nameInput'
                    />
                </div>
                <div>
                    <textarea
                        name="contents"
                        placeholder="내용"
                        value={formData.contents}
                        onChange={onChange}
                    />
                </div>

            <button onClick={onModify}>수정</button>
        </div>
    );
 };
};

export default BoardModify;

 

게시글 하나를 수정하기위해 server.js에 추가해야하는 구문

app.post('/modify/:id', (req, res) => {

    const { title, contents } = req.body;

    const sql = `update board set title = ? , contents = ? where id = ${req.params.id}`;

    db.query(sql, [title,contents], (err, data) => {
        if(!err){
            res.sendStatus(200); // 전송잘됨.
        } else {
            console.log(err);
            res.send('전송오류');
        }
    })
})