React 수업 정리

게시판 구현해보기(1)

shchan 2024. 9. 24. 13:17

배운것들을 활용해서 게시판을 만들어 보았다.

 

크게 4개의 컴포넌트로 구상하였는데

 

첫번째는 게시글 목록을 보여줄 List 컴포넌트, 두번째는 목록에서 제목을 누르면 내용을 보여줄 상세페이지

Detail컴포넌트, 세번째는 상세페이지에서 내용을 바꿀 수 있게 해주는 수정페이지 Modify 컴포넌트,

네번째는 게시글 목록에서 글쓰기버튼으로 게시글을 추가하게해주는 Create 컴포넌트이다.

 

일단 다 기본 틀만 만들어놓고  서버에 필요한것들을 다 인스톨 한 뒤

 

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!!');
});

 

그리고 화면이 나와야하니 App.js에 

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import List from './component/List';
import Detail from './component/Detail';
import Create from './component/Create';
import Modify from './component/Modify';


function App() {
  return (
    <BrowserRouter>
        <Routes>
            <Route path='/' element={<List />} />
            <Route path='/list' element={<List />} />
            <Route path='/detail/:id' element={<Detail/>} />
            <Route path='/create' element={<Create />} />
            <Route path='/modify/:id' element={<Modify/>} />
        </Routes>
    </BrowserRouter>
);
}

export default App;

 

화면이나오도록 , 누르면 페이지가 넘어 갈 수 있도록 코드를 짜준다.

 


 

게시글 목록 페이지 List 구현하기

 

일단 게시글 목록페이지에서 구현할 기능은 검색기능, 페이지네이션, 최신/조회순 정렬 크게 3가지이다.

 

 return (
            <div className='list'>
                <Link  style={{ textDecoration: "none"}} to={'/list'}><h2>자유게시판</h2></Link>
                <hr />
                
                       <table>
                <thead>
                    <tr>
                        <th>번호</th>
                        <th>제목</th>
                        <th>작성자</th>
                        <th>작성일</th>
                        <th>조회</th>
                    </tr>
                </thead>
                <tbody>
                  {
                     currentItems.map(b => (
                        

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

                        ))
                    }
                </tbody>
                </table>
                
         )

 

일단 화면에 표시할 항목들을 적어주고

  const [ list, setList ] = useState([]);
  
  const getListData = async () => {
        const lists = await axios.get('/list');
        setList(lists.data);
    }
    
     useEffect(() => {
        getListData();
    },[]);

 

app.get('/list', (req, res) => {
    console.log('/list');
    const sql = 'select * from free_board order by id desc';
    db.query(sql, (err, data) => {
        if(!err){
            res.send(data);
        } else {
            console.log(err);
            res.send('전송오류');
        }
    })
});

 

를 통해 데이터를 가져올 수 있게한다.

가져올 수 있게하기 위해 DB는 미리 만들어두었다

 

db의 구성은 이러하다. 

create table free_board (
id bigint auto_increment primary key,
title varchar(30),
contents varchar(500),
writer varchar(20),
reg_date timestamp DEFAULT now(),

viewCount int default 0
);

 

리스트를 만들면서 Create 전에 데이터를 확인할 수 있게 데이터를 몇개 넣어두자.

 

이제

app.post('/list/:id', async (req, res) => {
    const { id } = req.params;

    const sql = `update free_board set viewCount = viewCount + 1 where id = ${id}`;

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

 

를 통해 조회수가 상세페이지로 넘어가면서 +1  올라가도록 만들어주고.

 

검색기능을 구현해보도록하자.

 

 const [searchTerm, setSearchTerm] = useState('');

 const [filteredList, setFilteredList] = useState([]);

 const [searchOption, setSearchOption] = useState('title');

 

크게 3개의 상태를 정의하는데 

 

검색입력창의 상태, 검색하고 필터링된 리스트의 상태, 그리고 무엇으로 검색할건지의 대한 옵션의 상태를 정의해준다.

 

 const handleSearch = () => {
        if (searchTerm) {
            const filtered = list.filter(item => {
                if (searchOption === 'title') {
                    return item.title.includes(searchTerm);
                } else if (searchOption === 'writer') {
                    return item.writer.includes(searchTerm);
                } else if (searchOption === 'both') {
                    return item.title.includes(searchTerm) || item.writer.includes(searchTerm);
                }
                return false;
            });
            setFilteredList(filtered);
        } else {
            setFilteredList(list);
        }
        setSearchTerm('');
    };

 

정의 후 옵션의 상태에따라 포함 유무에따라 필터링 할 수 있도록 이렇게 짜준 후, 검색 후에 내용이 비워지도록 설정.

 

  const getListData = async () => {
        const lists = await axios.get('/list');
        setList(lists.data);
        setFilteredList(lists.data);
    }

 

데이터를 가져올 때도 setFilteredList에도 가져오도록 추가설정.

   const handleKeyPress = (event) => {
        if (event.key === 'Enter') {
            handleSearch();
        }
    };

 

엔터를 눌렀을 때도 검색이 가능하도록 추가설정해준다.

 

     <select value={searchOption} onChange={(e) => setSearchOption(e.target.value)}>
                <option value="title">제목</option>
                <option value="writer">작성자</option>
                <option value="both">제목+작성자</option>
                </select>

                <input 
                className='search'
                type="text" 
                placeholder="검색어 입력" 
                value={searchTerm} 
                onChange={(e) => setSearchTerm(e.target.value)} 
                onKeyDown={handleKeyPress}
            />

            <button className='searchBtn' onClick={handleSearch}>검색</button>

 

그리고 화면에 나오도록 이렇게 구현해주면 검색기능이 구현된다.

 

이제 게시글이 너무많으면 페이지가 생성되어 넘어갈 수 있도록 구현해보자.

이번에는 8개가 넘어가면 페이지가 생성되고 넘어가도록 구현해 보도록 하겠다.

 

  const [currentPage, setCurrentPage] = useState(1);
  
  const itemsPerPage = 8;

 

페이지의 상태와  한페이지 당 게시글의 수를 정의주도록 한다.

 

검색기능 구현에 setCurrentPage(1); 를 넣어서 검색 후 1페이지가 되도록 추가해주고.

 

   const totalPages = Math.ceil(filteredList.length / itemsPerPage);
   const startIndex = (currentPage - 1) * itemsPerPage;
   const currentItems = filteredList.slice(startIndex, startIndex + itemsPerPage);

 

전체페이지, 현재페이지, 시작인덱스를 정의해주도록 하자.

 

 const handlePageChange = (pageNumber) => {
        setCurrentPage(pageNumber);
    };


    const onPre = () => {
        if (currentPage > 1) {
            setCurrentPage(currentPage - 1);
        }
    };

    const onNext = () => {
        if (currentPage < totalPages) {
            setCurrentPage(currentPage + 1);
        }
    };

 

그러고 페이지 이동을 구현.

 

   <div className='pagination'>
                    <button onClick={onPre} disabled={currentPage === 1}>
                        이전
                    </button>

                    {Array.from({ length: totalPages }, (_, index) => (
                        <button 
                            key={index + 1} 
                            onClick={() => handlePageChange(index + 1)} 
                            disabled={currentPage === index + 1}
                        >
                            {index + 1}
                        </button>
                    ))}

                    <button onClick={onNext} disabled={currentPage === totalPages}>
                        다음
                    </button>

 

선택한 페이지와 1은 이전, 최대페이지면 다음 버튼을 disabled 해서 비활성화 시켜주면서 화면에 구성하면 

페이지네이션이 구현된다.

 

이제 정렬 기능만 넣으면 리스트 기능은 구현은 끝이난다.

 

    const [sort, setSort] = useState('latest');

 

정렬 상태를 일단 정의해주고

 

  const sortList = (list) => {
        return [...list].sort((a, b) => {
            if (sort === 'latest') {
                return new Date(b.reg_date) - new Date(a.reg_date);
            } else if (sort === 'views') {
                return b.viewCount - a.viewCount;
            }
            return 0;
        });
    };

 

sortList 기존 list를 스프레드로 받아와서 sort의 상태에따라 시간순, 조회순으로 정렬 할 수있도록 기능을 만들어준다.

   useEffect(() => {
        const sortedList = sortList(filteredList);
        setFilteredList(sortedList);
    }, [sort]);

 

sort가 바뀔때마다 필터링 되어서 정렬 할 수있도록 useEffect해준다.

 

     <div className='sort-container'>
                <div className='sort'>
                    <button 
                        className={sort === 'latest' ? 'active' : ''} 
                        onClick={() => setSort('latest')}
                    >
                        최신순
                    </button>
                    <button 
                        className={sort === 'views' ? 'active' : ''} 
                        onClick={() => setSort('views')}
                    >
                        조회순
                    </button>
                </div>
            </div>

 

이제 화면에서 최신순 , 조회순 버튼을 만들어주고 active 되면 버튼에 불이 들어오도록 css할 수 있게 active를 넣어주었다.

 

 


 

 

리스트 페이지 완성

 

페이지에 따라 게시글 목록이 넘어가고 검색기능, 최신/ 조회순으로 정렬 할 수 있는 기능이 구현된 것을 볼 수 있다.