반응형

이번 포스팅에서는 게시판에 필요한 테이블을 생성하고, 게시판 목록 조회하는 기능까지 구현해보겠다.

 

나는 전문적으로 테이블 설계를 해본 경험이 없기 때문에 테이블명, 칼럼명, 구조 등이 이상하게 보일 수 있다.

하지만 내가 이번 게시판 만들기를 공부하는 목적은 테이블을 완벽하게 설계하는 것이 아니기 때문에 무시하고 진행하도록 하겠다.

사실 설계라고 하기도 민망한 수준이지만.. ㅎㅎ

혹시라도 완벽하게 하고 싶으신 분들은 테이블을 따로 설계하시면 됩니다.

 

그럼 테이블을 만들어보자.

테이블은 정말 단순하게 1개만 생성한다.

 

a) 게시판 테이블

 

1. MySQL Workbench를 실행한다.

 

2. 테이블을 만든다.

나중에 이 글을 다시 읽어볼 나를 위해..

혹시 나와 같이 기초부터 공부를 하고 있을 누군가를 위해..

 

반응형

 

테이블 생성 설명!!

테이블 생성 전 내가 어느 데이터베이스를 사용할 것인지 지정해주어야 한다.

게시글 번호는 수정, 삭제, 상세조회에서 사용할 고유 ID이기 때문에 빈 값이나 중복된 값을 허용해서는 안된다.

auto_increment 속성을 추가해서 자연스럽게 ID값이 증가하도록 하였다.

 

내용(CONTENT) 칼럼은 html을 그대로 저장하는 방법으로 진행하려고 한다.

지금은 단순 텍스트만 있으니 굳이 그럴 필요는 없겠지만 혹시 모르니까..

 

use bbs; // bbs db 사용

create table BOARD (
	BOARD_ID int not null auto_increment primary key,
        BOARD_TITLE nvarchar(30),
	BOARD_CONTENT nvarchar(500),
        REGISTER_ID nvarchar(20),
        REGISTER_DATE DATETIME DEFAULT now(),
        UPDATER_ID nvarchar(20),
        UPDATER_DATE DATETIME DEFAULT now()
);

 

테이블이 생성은 되었지만 경고가 떴다.

NATIONAL/NCHAR/NVARCHAR implies the character set UTF8MB3, which will be replaced by UTF8MB4 in a future release. Please consider using CHAR(x) CHARACTER SET UTF8MB4 in order to be unambiguous.

 

우선 무시하고 넘어가 보자.

 

 

그다음, 목록을 조회하는 서버 코드를 작성하자.

기존에 테스트용으로 작성했던 소스 코드와 테이블은 삭제해도 무방하다.

 

1. 서버 프로젝트의 index.js 파일에 아래 API 코드 추가

 

app.get("/list", (req, res) => {
  const sqlQuery = "SELECT *FROM BOARD;";
  db.query(sqlQuery, (err, result) => {
    res.send(result);
  });
});

 

 

2. 게시판 테이블에 테스트 데이터 생성

Workbench에서 아래 쿼리를 한 줄씩 실행하여 조회 가능한 데이터를 생성하자.

 

insert into BOARD(BOARD_TITLE, BOARD_CONTENT, REGISTER_ID) values('제목1', '내용1', 'artistJay');
insert into BOARD(BOARD_TITLE, BOARD_CONTENT, REGISTER_ID) values('제목2', '내용2', 'artistJay');

 

3. 생성된 데이터 확인

방금 생성한 2개의 데이터가 보이는지 확인한다.

 

select *from BOARD;

만약 데이터가 표시되지 않는다면!!!

Query > Auto-Commit Transactions 가 체크 해제되어있는지 확인하자.

만약 해당 기능이 해제되어 있다면 별도로 commit 명령어를 실행해야 한다.

 

 

 

마지막으로 클라이언트 소스 코드를 수정하면 된다.

BoardList.tsx 파일만 수정하였다.

 

수정된 내용: 배열 렌더링에 사용할 Board 메서드 추가, state 추가, 목록을 가져오는 메서드 추가, componentDidMount 추가, render 함수 부분 수정

 

componentDidMount라는 것은 컴포넌트의 인스턴스가 생성되어 DOM 상에 삽입될 때에 호출되는 메서드 중에 하나이다. 주로, 화면이 뜬 이후에 바로 렌더링 해야 하는 데이터들(목록, 상태 조회 등)을 가져와야 할 때 해당 메서드 안에 axios를 쓰는 것 같다.

 

우선.. 이거 하나 동작시키는데 매우 어려움이 있었다.

기본적으로 react로 제대로 된 컴포넌트를 만들고 렌더링 해본 경험이 없기 때문에.. 어려웠다.

 

다른 방법들도 있는 것 같은데, 가장 이해가 되는 방법으로 코딩을 했다..

 

전체 소스

 

import { Component } from "react";
import Axios from "axios";
import Table from "react-bootstrap/Table";
import Button from "react-bootstrap/Button";

const Board = ({
    id,
    title,
    registerId,
    registerDate,
}: {
    id: number;
    title: string;
    registerId: string;
    registerDate: string;
}) => {
    return (
        <tr>
            <td>
                <input type="checkbox"></input>
            </td>
            <td>{id}</td>
            <td>{title}</td>
            <td>{registerId}</td>
            <td>{registerDate}</td>
        </tr>
    );
};

/**
 * BoardList class
 */
class BoardList extends Component {
    state = {
        boardList: [],
    };

    getList = () => {
        Axios.get("http://localhost:8000/list", {})
            .then((res) => {
                const { data } = res;
                this.setState({
                    boardList: data,
                });
            })
            .catch((e) => {
                console.error(e);
            });
    };

    /**
     */
    componentDidMount() {
        this.getList();
    }

    /**
     * @return {Component} Component
     */
    render() {
        // eslint-disable-next-line
        const { boardList }: { boardList: any } = this.state;

        return (
            <div>
                <Table striped bordered hover>
                    <thead>
                        <tr>
                            <th>선택</th>
                            <th>번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>작성일</th>
                        </tr>
                    </thead>
                    <tbody>
                        {
                            // eslint-disable-next-line
                            boardList.map((v: any) => {
                                return (
                                    <Board
                                        id={v.BOARD_ID}
                                        title={v.BOARD_TITLE}
                                        registerId={v.REGISTER_ID}
                                        registerDate={v.REGISTER_DATE}
                                    />
                                );
                            })}
                    </tbody>
                </Table>
                <Button variant="info">글쓰기</Button>
                <Button variant="secondary">수정하기</Button>
                <Button variant="danger">삭제하기</Button>
            </div>
        );
    }
}

export default BoardList;

 

나중에 다시 봤을 때 까먹을 것 같아서 소스 설명을 추가하겠다.

 

Board 메서드: 리스트를 파라미터로 넘겨받아 반복할 Element를 return

 

// eslint-disable-next-line
const { boardList }: { boardList: any } = this.state;

 

이건 매우 개인적인 건데, 위에 주석을 안 하면 자꾸 eslint에서 경고가 뜬다.

Unexpected any. Specify a different type.

 

너무 불편해서 이게 맞는 건가 싶었다. 설정에서 무시하는 방법도 있는 것 같지만 우선 임시 조치하는 걸로..

 

render() {
    // eslint-disable-next-line
    const { boardList }: { boardList: any } = this.state;

    return (
        <div>
            <Table striped bordered hover>
                <thead>
                    <tr>
                        <th>선택</th>
                        <th>번호</th>
                        <th>제목</th>
                        <th>작성자</th>
                        <th>작성일</th>
                    </tr>
                </thead>
                <tbody>
                    {
                        // eslint-disable-next-line
                        boardList.map((v: any) => {
                            return (
                                <Board
                                    id={v.BOARD_ID}
                                    title={v.BOARD_TITLE}
                                    registerId={v.REGISTER_ID}
                                    registerDate={v.REGISTER_DATE}
                                />
                            );
                        })}
                </tbody>
            </Table>
            <Button variant="info">글쓰기</Button>
            <Button variant="secondary">수정하기</Button>
            <Button variant="danger">삭제하기</Button>
        </div>
    );
}

 

이건 렌더링을 하는 부분이다.

태그를 메서드명으로 하고 파라미터를 세팅하면 위에서 선언한 메서드의 return값으로 렌더링이 되는 것 같다.

실제로는 좀 더 고급스럽게 표현할 수 있겠지만, 아직은 기초지식이 부족하니 이 정도로 설명하겠다.

 

실제 코딩을 하면서 이 부분에서 굉장한 스트레스를 받았는데.. 'v.BOARD_ID' 이 부분에서 계속 아래 에러가 발생했었다.

property 'board_id' does not exist on type 'never'

boardList의 타입을 설정하니 정상적으로 됐지만.. 힘들었다.

 

 

 

이제 클라이언트와 서버를 모두 켜서 동작을 확인해보자.

 

 

 

렌더는 정상적으로 된 것 같지만 작성일이 마음에 들지 않는다.

 

쿼리를 수정하자.

서버 프로젝트의 index.js 소스 코드를 아래와 같이 수정한다.

 

// 기존
app.get("/list", (req, res) => {
  const sqlQuery = "SELECT *FROM BOARD;";
  db.query(sqlQuery, (err, result) => {
    res.send(result);
  });
});

// 변경
app.get("/list", (req, res) => {
  const sqlQuery = "SELECT BOARD_ID, BOARD_TITLE, REGISTER_ID, DATE_FORMAT(REGISTER_DATE, '%Y-%m-%d') AS REGISTER_DATE FROM BOARD;";
  db.query(sqlQuery, (err, result) => {
    res.send(result);
  });
});

 

그리고 화면을 refresh 해서 변경사항을 확인한다.

 

 

끝인 줄 알았는데 콘솔을 확인해보니, 에러가 발생하고 있었다.

Each child in a list should have a unique "key" prop.

 

원인

React는 key prop을 사용하여 컴포넌트와 DOM 요소 간의 관계를 생성한다. 리액트 라이브러리는 이 관계를 이용해 컴포넌트 리 렌더링 여부를 결정한다. 불필요한 리 렌더링을 방지하기 위해서는 각 자식 컴포넌트마다 독립적인 key값을 넣어줘야 한다.

 

해결방법

간단하게 렌더링 하는 부분에 key 값을 넣어주었다.

 

끝!!

이제 상세조회, 글쓰기, 수정, 삭제 남았다!

반응형