목차 | |
1. | 게시판 |
2. | 느낀 점 |
1. 게시판
▣ board-app
- App.js
import './App.css';
import BoardHome from './component/BoardHome';
function App() {
return (
<div className="App">
<BoardHome/>
</div>
);
}
export default App;
- sever.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!!")
})
// 게시글 목록 가져오기
app.get('/list', (req, res) => {
// console.log('/list');
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('전송오류');
}
});
});
// 게시글 하나 가져오기 : id
// 화면에서 서버로 요청하는 값 : request (req)
// 서버에서 화면으로 보내주는 값 : response (res)
// 화면에서 가져온 파라미터 추출 : req.params.id
app.get('/view/:id', (req, res) => {
// 파라미터 가져오기
const id = req.params.id;
console.log(`/view/${id}`);
const sql = `select * from board where id = ${id}`;
db.query(sql, (err, data) => {
if(!err){
res.send(data);
}else{
console.log(err);
res.send("전송오류");
}
})
});
// board 등록
app.post('/insert', (req, res) => {
// 파라미터 가져오기 requset.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("OK");
res.sendStatus(200); // 전송 잘됨
}else{
console.log(err);
res.send("전송오류");
}
})
});
// 수정-불러오기
app.get('/modify/:id', (req, res) => {
// 파라미터 가져오기
const id = req.params.id;
console.log(`/modify/${id}`);
const sql = `select * from board where id = ${id}`;
db.query(sql, (err, data) => {
if(!err){
res.send(data);
}else{
console.log(err);
res.send("전송오류");
}
})
});
// 수정-저장
app.post('/modify/:id', (req, res) => {
// 파라미터 가져오기 requset.body
// const board = req.body;
// board.title
const id = req.params.id;
const { title, writer, contents } = req.body;
const sql = `update board set title=?, writer=?, contents=? where id=?`;
db.query(sql, [title, writer, contents, id], (err, data) => {
if(!err){
// res.send("OK");
res.sendStatus(200); // 전송 잘됨
}else{
console.log(err);
res.send("전송오류");
}
})
});
// 삭제
app.post('/delete/:id', (req, res) => {
const id = req.params.id;
const sql = `delete from board where id=${id}`;
db.query(sql, (err, data) => {
if(!err){
// res.send("OK");
res.sendStatus(200); // 전송 잘됨
}else{
console.log(err);
res.send("전송오류");
}
})
});
- BoardHome.jsx
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import BoardList from './BoardList';
import '../component/board-style.css';
import BoardDetail from './BoardDetail';
import BoardRegister from './BoardRegister';
import BoardModify from './BoardModify';
const BoardHome = () => {
return (
<div className='boardHome'>
<h1 className='title'>My First React Board Project</h1>
<hr />
<BrowserRouter>
<Routes>
<Route path='/' element={<BoardList/>}/>
<Route path='/list' element={<BoardList/>}/>
<Route path="/detail/:id" element={<BoardDetail/>} />
<Route path='/register' element={<BoardRegister/>}/>
<Route path='/modify/:id' element={<BoardModify/>}/>
</Routes>
</BrowserRouter>
</div>
);
};
export default BoardHome;
- BoardList.jsx
import React, { useState } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { useEffect } from 'react';
const BoardList = () => {
// db에 저장되어 있는 board 요소를 가져오기 => boardList 저장
const [ boardList, setBoardList ] = useState({});
// 비동기로 db에 접속하여 select로 가져오기
// get : 데이터 가져올 때 (생략 가능)
// post : 데이터를 보낼 때 (반드시 써야 함)
const getBoardData = async () => {
try{
const boards = await axios('/list');
console.log(boards);
setBoardList(boards.data);
} catch (error) {
console.log(error);
}
}
// 컴포넌트가 랜더링 될 때, 혹은 업데이트 될 때 실행되는 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 className='subTitle'>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
{boardList.map(b => (
<tr key={b.id}>
<td className='center'>{b.id}</td>
<td><Link to={`/detail/${b.id}`}>{b.title}</Link></td>
<td>{b.writer}</td>
<td>{b.reg_date.substring(0, b.reg_date.indexOf("T"))}</td>
</tr>
))}
</tbody>
</table>
<Link to={`/register`}><button className='textButton'>글쓰기</button></Link>
</div>
);
}
};
export default BoardList;
- BoardDetail.jsx
import React, { useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import axios from 'axios';
// import { boardList } from '../data/data';
const BoardDetail = () => {
const { id } = useParams();
// 특정 조건을 만족하는 요소의 index를 찾는 함수 findIndex()
// boardList.findIndex(b => b.id === Number(id))
// params는 String으로 값을 가져옴 ==> 따라서 Number로 형변환
// 굳이 findIndex를 사용하는 이유는 id의 값과 index(boardList의 index)가 맞지 않기 때문
// const idx = boardList.findIndex(b => b.id === Number(id));
// console.log(idx);
// const board = boardList[idx];
// console.log(board);
const [ board, setBoard ] = useState(null);
const getBoard = async () => {
try{
const res = await axios(`/view/${id}`);
// res.data : 데이터가 1개 더라도 배열로 돌어옴
setBoard(res.data[0]);
console.log(res);
}catch(error){
console.log(error);
}
};
useEffect(()=>{
getBoard();
},[]);
const onDelete = async () => {
if(window.confirm('삭제하시겠습니까?')){
try{
await axios.post(`/delete/${id}`);
window.location.href = `/list`;
}catch(error){
console.log(error);
}
}
};
if(board != null){
return (
<div className='boardDetailBoard'>
<h2>No.{board.id} / Board Detail Page</h2>
<div className='boardDetailContainer'>
<span className='boardTitle'>{board.title}</span>
<span className='bold'>{board.writer} [{board.reg_date.substring(0, board.reg_date.indexOf("T"))}]</span>
<div>{board.contents}</div>
</div>
<div>
<Link to={`/modify/${board.id}`}><button>Modify</button></Link>
<button onClick={onDelete}>Remove</button>
<Link to={'/list'}><button>List</button></Link>
</div>
</div>
);
}
};
export default BoardDetail;
- BoardRegister.jsx
import React, { useState } from 'react';
import axios from 'axios';
const BoardRegister = () => {
const [ board, setBoard ] = useState({
title: '',
writer: '',
contents: ''
});
const { title, writer, contents } = board;
const onChange = (e) => {
const { name, value } = e.target;
setBoard({
...board,
[name]:value
});
}
const onReset = () => {
setBoard({
...board,
title: '',
writer: '',
contents: ''
})
}
const onCreate = async () => {
// board 객체를 서버로 전송
// board 객체의 내용 중 하나라도 null이면 안 됨.
if(title === ''){
alert('title is null');
return;
}
if(writer === ''){
alert('wirter is null');
return;
}
if(contents === ''){
alert('contents is null');
return;
}
if(window.confirm('등록하시겠습니까?')){
try{
const res = await axios.post('/insert', board);
console.log(res);
// if(res.data[0] === 'OK'){}
// 데이터 전송 후 이동
window.location.href = "/list";
}catch(error){
console.log(error);
}
}
}
return (
<div className='boardRegister'>
<h2>Board Register</h2>
<div className='content'>
<input type="text" className='content-box' name='title' value={title} placeholder='Title' onChange={onChange}/>
<input type="text" className='content-box' name='writer' value={writer} placeholder='Writer' onChange={onChange}/>
<div className='contentContainer'>
<textarea type="text" className='content-box' name='contents' value={contents} placeholder='Contents' onChange={onChange}/>
</div>
</div>
<button onClick={onCreate}>Register</button>
<button onClick={onReset}>Init</button>
</div>
);
};
export default BoardRegister;
- BoardModify.jsx
// 설치한 라이브러리 변수로 받아오기
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!!")
})
// 게시글 목록 가져오기
app.get('/list', (req, res) => {
// console.log('/list');
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('전송오류');
}
});
});
// 게시글 하나 가져오기 : id
// 화면에서 서버로 요청하는 값 : request (req)
// 서버에서 화면으로 보내주는 값 : response (res)
// 화면에서 가져온 파라미터 추출 : req.params.id
app.get('/view/:id', (req, res) => {
// 파라미터 가져오기
const id = req.params.id;
console.log(`/view/${id}`);
const sql = `select * from board where id = ${id}`;
db.query(sql, (err, data) => {
if(!err){
res.send(data);
}else{
console.log(err);
res.send("전송오류");
}
})
});
// board 등록
app.post('/insert', (req, res) => {
// 파라미터 가져오기 requset.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("OK");
res.sendStatus(200); // 전송 잘됨
}else{
console.log(err);
res.send("전송오류");
}
})
});
// 수정-불러오기
app.get('/modify/:id', (req, res) => {
// 파라미터 가져오기
const id = req.params.id;
console.log(`/modify/${id}`);
const sql = `select * from board where id = ${id}`;
db.query(sql, (err, data) => {
if(!err){
res.send(data);
}else{
console.log(err);
res.send("전송오류");
}
})
});
// 수정-저장
app.post('/modify/:id', (req, res) => {
// 파라미터 가져오기 requset.body
// const board = req.body;
// board.title
const id = req.params.id;
const { title, writer, contents } = req.body;
const sql = `update board set title=?, writer=?, contents=? where id=?`;
db.query(sql, [title, writer, contents, id], (err, data) => {
if(!err){
// res.send("OK");
res.sendStatus(200); // 전송 잘됨
}else{
console.log(err);
res.send("전송오류");
}
})
});
// 삭제
app.post('/delete/:id', (req, res) => {
const id = req.params.id;
const sql = `delete from board where id=${id}`;
db.query(sql, (err, data) => {
if(!err){
// res.send("OK");
res.sendStatus(200); // 전송 잘됨
}else{
console.log(err);
res.send("전송오류");
}
})
});
- board-style.css
.boardList {
font-family: Arial, sans-serif;
margin: 20px;
}
.title{
background-color: black;
color: transparent;
-webkit-background-clip: text;
margin-bottom: 30px;
}
h2 {
text-align: center;
color: #667eea;
}
table {
width: 1024px;
border-collapse: collapse;
margin: 20px auto;
}
.subTitle{
background: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
}
.subTitle>th{
color: #764ba2;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.center{
text-align: center;
}
th {
color: #333;
}
tr:nth-child(even) {
background-color: #F9F9F9;
}
tr:hover {
background: linear-gradient(to right, rgba(135, 207, 235, 0.452), rgba(255, 192, 203, 0.479));
color: #667eea;
font-weight: 700;
}
table {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
/* th:nth-child(1), td:nth-child(1) {
width: 3%;
word-break: break-word;
} */
.textButton{
width: 150px;
height: 60px;
color: white;
font-size: 20px;
font-weight: 700;
/* border 그라데이션 - button */
border: 1px solid transparent;
border-radius: 20px;
background-image: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
background-origin: border-box;
background-clip: content-box, border-box;
}
.textButton:hover{
cursor: pointer;
transform: scale(1.05);
}
.textButton:active {
transform: scale(0.95);
}
/* BoardDetail */
a{
text-decoration-line: none;
}
.boardDetailContainer{
font-family: Arial, sans-serif;
margin: 40px;
}
.boardDetailBoard button{
width: 150px;
height: 60px;
color: white;
font-size: 20px;
font-weight: 700;
/* border 그라데이션 - button */
border: 1px solid transparent;
border-radius: 20px;
background-image: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
background-origin: border-box;
background-clip: content-box, border-box;
}
button>a{
color: white;
}
.boardDetailBoard button:hover{
cursor: pointer;
transform: scale(1.05);
}
.boardDetailBoard button:active {
transform: scale(0.95);
}
.boardDetailContainer{
margin: 0 auto;
width: 1000px;
/* border 그라데이션 */
border: 2px solid transparent;
border-radius: 15px;
background-image: linear-gradient(#fff, #fff), linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
background-origin: border-box;
background-clip: content-box, border-box;
}
.boardDetailContainer>span{
padding: 20px 20px 10px 5px;
}
.boardDetailContainer>div{
margin: 20px 0 20px 0;
}
.bold{
font-size: 15px;
font-weight: bold;
}
/* BoardRegister */
.content{
width: 1024px;
margin: 0 auto;
/* border 그라데이션 */
border: 2px solid transparent;
border-radius: 15px;
background-image: linear-gradient(#fff, #fff), linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
background-origin: border-box;
background-clip: content-box, border-box;
}
.content>.content-box{
width: 100px;
height: 45px;
padding: 10px;
border: none;
outline: none;
font-size: 15px;
font-weight: 500;
margin: 0 20px 0 33px;
}
.content>.content-box:first-child{
margin-left: 34px;
}
.content>.content-box:nth-child(2){
width: 250px;
}
.content>div>.content-box{
width: 800px;
height: 300px;
padding: 10px;
border: 1px solid pink;
outline: none;
font-size: 15px;
font-weight: 500;
margin: 10px;
}
.contentContainer{
background-color: rgba(255, 192, 203, 0.322)
}
.boardRegister button{
width: 150px;
height: 60px;
color: white;
margin-top: 20px;
font-size: 20px;
font-weight: 700;
/* border 그라데이션 - button */
border: 1px solid transparent;
border-radius: 20px;
background-image: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
background-origin: border-box;
background-clip: content-box, border-box;
}
.boardRegister button:hover{
cursor: pointer;
transform: scale(1.05);
}
.boardRegister button:active {
transform: scale(0.95);
}
- package.json
{
"name": "board-app",
"version": "0.1.0",
"private": true,
"proxy" : "http://localhost:5000/",
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.7",
"cors": "^2.8.5",
"express": "^4.21.0",
"json": "^11.0.0",
"mysql": "^2.18.1",
"nodemon": "^3.1.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.2",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
▷ 출력
2. 느낀 점
저번 프로젝트에서 게시판을 만들어보고 싶었는데 이번 기회에 만들어볼 수 있어서 좋았다.
'Front-end > React' 카테고리의 다른 글
React 기초(db 연동)- AWS 풀스택 과정 42일차 (0) | 2024.09.19 |
---|---|
React 기초(useReducer)- AWS 풀스택 과정 41일차 (0) | 2024.09.10 |
React 기초(todoList)- AWS 풀스택 과정 40일차 (0) | 2024.09.09 |
React 기초(배열)- AWS 풀스택 과정 39일차 (0) | 2024.09.06 |
React 기초(input, param)- AWS 풀스택 과정 38일차 (0) | 2024.09.05 |