Board 만들어보기
게시글이 추가, 수정 , 삭제가 가능한 게시판을 구현해보기.
- 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('전송오류');
}
})
})