[React] 익명게시판 기능 만들기

김유진·2022년 7월 6일
0

React

목록 보기
8/64

1. 글쓰기 버튼 활성화, Router를 이용하여 글 리스트 루트에 넣어두기!

먼저 React Router 라이브러리를 설치한다.
그런 다음 Router가 잘 깔려 있는지 확인한 이후,
index.js로 들어가 다음 사전작업을 마치자.

import { BrowserRouter } from 'react-router-dom'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

라우터를 이용하기 위한 사전 작업을 마쳤으니, 이제 실제로 write와 post를 열람하는 post 페이지를 만들어 보자. src 폴더에다가 ShowPost.jsxWritePost.jsx파일을 만든다. 그리고 rsc를 이용하여 기본 컴포넌트를 생성하면 된다.

이제 이 친구들을 만들어 주었으니까, 링크를 연결해야 한다. 즉 App.jsx 파일로 들어가서 다음과 같이 코드를 작성한다.

...
import ShowPost from './ShowPost';
import WritePost from './WritePost';
...
function App() {
    const [darkMode, setDarkMode] = useState(true);
    return (
        <>
            <ThemeProvider theme ={darkMode ? darkTheme : lightTheme}>
                <GlobalStyles/>
                <MediaDiv>
                    <Header darkMode = {darkMode} setDarkMode = {setDarkMode}/>
                    <Main>
                        <Slogun></Slogun>
                        <Routes>
                            <Route path = "/" element = {<ShowPostList></ShowPostList>}></Route>
                            <Route path = "/write" element = {<WritePost></WritePost>}></Route>
                            <Route paht = "/post/:postID" element = {<ShowPost></ShowPost>}></Route>
                        </Routes>
                    </Main>
                    <Footer />
                </MediaDiv>
            </ThemeProvider>    
        </>
    )
}
...

여기서 기존에 있던, props로 값을 넘겨주던 ShowPostList를 Route의 element로 치환해주었다. ShowPostList의 라우터 연결해주었으니, 그쪽에서 책임지라고 정보를 다 보내준 것이다.
그리고 새롭게 링크를 만들었는데, 그것은 writepost이다. 단 post는 각 포스트의 고유한 ID에 따라서 달라져야 하므로 세미콜론을 붙이어 아이디를 구별할 수 있게 만들었다.

ShowPostList.jsx 의 코드를 이제 자세히 뜯어보자.

import { useNavigate } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import EachPost from './EachPost';

const initialPostList = [
    {id : 1, title : '리액트 공부는 재미있어.', replCount : 1},
    {id : 2, title : '프론트 공부 열심히 해.', replCount : 43},
    {id : 3, title : '파이팅^^.', replCount : 1},
];


function ShowPostList(){
    const [loading, setLoading] = useState(false);
    const [isPost, setIsPost] = useState(false)
    const [postList, setPostList] = useState(initialPostList);

    const addPost = () => {
        setPostList((postList) => [
            ...postList, {id : 4, title :'공부는 열심히 하면 좋아.', replCount : 21},
        ]);
    };
    
    const navigate = useNavigate();

    const goWrite = () => {
        navigate('/write');
    };
  ...

지난 포스트에서 배웠던 navigate를 이용하여 글쓰기 페이지로 넘어갈 수 있도록 링크를 설정하였다. 또한 initialPostList도 넘겨받아 두게 하였다. 여기서 주의할 점은 useNavigate를 사용하려면 미리 import 해두어야 한다는 점~!

그리고 글쓰기 버튼에 마우스를 올리면 hover 처리가 되어야 하므로 `styledComponent에도 다음 내용을 추가하였다.

export const CursorDiv = styled.div`
    cursor : pointer;
`;

이 처리를 해야지 글쓰기 버튼에 마우스를 올려두었을 때, 진짜 올려둔 것처럼 처리가 된다.

2. 포스트 리스트 로딩화면 만들기

잠깐! 우리가 상셉보기 페이지를 만들기 전에 알아두어야 하는 것이 있다. 그것은 바로

useEffect()

이 친구는 리액트 컴포넌트가 랜더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook이다.
형태는 다음과 같이 쓰일 수 있다.

useEffect(function, deps)

  • function은 수행하고자 하는 작업
  • deps는 검사하고자 하는 값 또는 배열, 배열형태

① 가장 처음 렌더링 될 때 한번만 실행하고 싶음 - 빈 배열을 넣기

useEffect(function, [])

② 특정 props나 state가 바뀔 때 실행 - 특정 값 넣기

useEffect(function, [바뀌는 값])

useEffect를 한번 사용하여 보자. 먼저 import 시켜야겠지요? 우리가 이것을 이용하여서 무엇을 할 것이나면,
처음 PostList를 로딩하는 화면을 보여주기 위하여 useEffect를 이용할 것이다. 로딩이 다 끝나면, postList를 보여줄 것인데, setTimeout으로 랜더링 되기까지의 시간을 늘려야 한다.

    useEffect(() => {
        setTimeout(() => {
            setPostList(initialPostList)
            setLoading(false);
        }, 5000)
    },[]); 

처음 로딩시간을 5초로 설정하였고, 로딩이 완료된 시점에서 setLoading 의 값을 거짓으로 잡아야 하기 때문에 이렇게 작성한 것이다. 우리는 페이지를 랜더링할때 처음만 할 것이니까 빈 배열을 넣어둔 것!!

 <PostListDiv>
                        {loading ? (
                                <LoadingDiv>
                                    <LoadingImg src = {'./loading.svg'}/>
                                </LoadingDiv>
                            ) : isPost ? (
                                    <LoadingDiv>
                                        아직 기록된 글이 없습니다.
                                    </LoadingDiv>
                            ) : (
                                <ul>
                                    {postList.map((element) => (
                                        <EachPost 
                                        key={element.id} 
                                        title = {element.title}
                                        replCount = {element.replCount}
                                        />
                                    ))}
                                </ul>
                            )}
                    </PostListDiv>

로딩이 참일 때는 실행화면이 보여지고, 로딩이 false일때는 글의 목록이 보이도록 코드를 설정해 둔 것이다.

3. 상세보기 페이지 만들기

게시글을 클릭하면 상세페이지로 넘어가도록 만들어 보자.

먼저 게시글의 제목을 누르면 해당 포스트로 넘어가야 하므로, 그 코드를 EachPost.jsx에 추가한다.

function EachPost({title, postID}) {
    const navigate = useNavigate();
    const goPost = () => {
        navigate(`${'/post/' + postID }`);
    };
    return (
        <EachPostLi onClick={goPost}>
            <div>
                <FontAwesomeIcon icon ={faLocationPin} />
                <PostLink>
                    {title}
                </PostLink>
            </div>
        </EachPostLi>
    );
}

추가한 코드는 위와 같다. useNavigate를 이용하니까 꼭 import 해주어야 한다! onClick 이벤트를 통하여 해당 포스트에 대한 자세한 내역이 담긴 페이지로 이동할 수 있는 것이다.

일단 세부 내용에 대한 정리가 필요하니 Showpost.jsx 페이지에 다음 코드를 추가하여 주었다.

...
const postData = {
  title: `바운스`,
  contents: `아기사자가 돌아서면 두 눈이 마주칠까, 심장이 bounce, bounce 두근 대 들릴까 봐 겁나
  한참을 망설이다 용기를 내 밤새워 준비한 내 개사 들어줘, 처음 본 순간부터 아기사자랑 친해질꺼야 생각했어~~,
  Baby, you're my trampoline You make me bounce Bounde - 아기사자들은 다 귀여워 최고 -
  `,
};

const replData = [
  { id: 2, content: `반가워요!` },
  { id: 3, content: `멋쟁이 사자처럼 최고!` },
];

const ShowPost = () => {
  const [post, setPost] = useState(null);
  const [repls, setRepls] = useState([]);
  const [postLoading, setPostLoading] = useState(true);
  const [replLoading, setReplLoading] = useState(true);
  ...

이렇게 코드를 추가하여 주고, 글창과 댓글창을 분리하기 위하여 둘의 로딩시간을 useEffect로 다르게 한다. 이 때 로딩을 보기 위하여 현재의 useState 값을 null과 빈 값으로 두고, 로딩은 true로 변수를 설정해 둔 것이다.

useEffect(() => {
   setTimeout(() => {
       setPost(postData);
       setPostLoading(false);
   }, 1000);
 });

 useEffect(() => {
   setTimeout(() => {
       setRepls(replData);
       setReplLoading(false);
   }, 3000);
 });

그리고 나머지 showpost 부분들 다음과 같이 작성한다.

  return (
    <div>
      <PostSection>
        <PostTitleDiv>
          <PostTitle>{post && post.title}</PostTitle>
        </PostTitleDiv>

        {postLoading ? (
          <LoadingDiv>
            <LoadingImg src={`${process.env.PUBLIC_URL}/img/loading.svg`} />
          </LoadingDiv>
        ) : (
          <PostReplDiv>{post && post.contents}</PostReplDiv>
        )}

        {/* post contents */}

        <ReplTitleDiv>댓글 {replCount} </ReplTitleDiv>
        {replLoading ? (
          <LoadingDiv>
            <LoadingImg src={`${process.env.PUBLIC_URL}/img/loading.svg`} />
          </LoadingDiv>
        ) : (
          repls &&
          repls.map((element) => (
            <PostReplDiv key={element.id}>
              <ReplWriter>익명</ReplWriter>
              <Repl>{element.content}</Repl>
            </PostReplDiv>
          ))
        )}

        <WriterDiv>
          <ReplInput onChange = {onChange} value = {repl}></ReplInput>
          <ReplSubmitDiv>
            <span>입력</span>
          </ReplSubmitDiv>
        </WriterDiv>
      </PostSection>
    </div>
  );
};

export default ShowPost;

4. 댓글 개수 세기

댓글 개수를 세기 전에 알아야 하는 개념이 있다.

useMemo

성능 최적화를 위하여, 연산된 값을 재사용하게 해주는 Hook이다.

useMemo(function,deps)

  • function은 어떤 연산을 할 지 정의하는 함수
  • deps는 검사하고자 하는 값 또는 배열, 배열형태

업데이트가 필요한 컴포넌트까지 랜더링하면 성능이 떨어지는데, 이를 방지하기 위하여 사용되는것이 useMemo이다.

아래의 특정 값이 바뀌면 함수를 호출하여 연산하고, 값이 바뀌지 않으면 재사용합니다.

useMemo(funtion, [특정 값])

  const countRepls = (repls) => {
    console.log('리뷰 개수를 세는 중...');
    return repls.length;
  }

  const replCount = countRepls(repls);

위는 useMemo를 사용하기 전의 코드이다.
아래 사진을 보면, useMemo를 이용하지 않고 댓글의 개수를 세는 장면이다. 이렇게 되면, 여러번 랜더링을 해야 하기 때문에 당연히 성능이 떨어지게 된다.

하지만 코드를 다음과 같이 ShowPost.jsx에서 바꾸어보자.

  const countRepls = (repls) => {
    console.log('리뷰 개수를 세는 중...');
    return repls.length;
  }

  const replCount = useMemo(() => countRepls(repls), [repls]);

여기서 repls는 위에서 설정한 변수인 것이다. [repls]의 값이 변화가 없으면 랜더링을 또 하지 않는 것이다.


댓글이 두개이므로 위의 사진처럼 랜더링이 2번만 일어난다. (이전의 2번 해서 4번처럼 보이는 것)

5. 댓글, 글쓰기 페이지 만들기

일반적인 <form>의 방식

<form>
	<input type = "text" name = "id"/>
</form>

input 창에 id값을 입력하면 id = 입력한 id값이었다.
하지만 React에서는 대부분 State를 사용하는 제어를 한다. 이를 제어 컴포넌트라고 한다. React에서 폼(<input>, <textarea>,<select>)에 발생하는 사용자 입력값을 제어하는 방식이다.

댓글을 어떻게 입력할까?

먼저 React는 상태관리를 위하여 state를 이용해야 한다. ShowPost.jsx에 다음 코드를 추가해보자.

  const [repl, setRepl] = useState('');

  const onChange = (e) => {
    setRepl(e.target.value);
  }

useState를 통하여 어떤 값이 입력되는지 실시간으로 확인할 수 있다. 그리고 onChange함수를 통하여, value값이 타겟인 repls에 저장될 수 있도록 이벤트를 발생시키는 역할을 한다.

그리고 하단의 코드를 보면,

 <WriterDiv>
          <ReplInput onChange = {onChange} value = {repl}></ReplInput>
          <ReplSubmitDiv>
            <span>입력</span>
          </ReplSubmitDiv>
        </WriterDiv>
      </PostSection>
    </div>

이렇게, onChange함수를 통하여 해당 이벤트를 발생시켜 값을 저장시킴을 알 수 있다.

그럼 정상적으로 댓글이 작성되고, 실시간으로 그 값이 저장됨을 알 수 있다.

제목과 글이 있는 게시글은 어떻게 작성하게 될까?

이 친구는 조금 더 까다롭다고 느낄 수 있는게, input이 두 군데나 있다!
WritePost.jsx를 작성해보자.

import React, { useState } from 'react';
import {
  ContentsInput,
  PostSection,
  PostSubmit,
  PostSubmitDiv,
  PostTitle,
  PostTitleDiv,
  PostWriteDiv,
  TitleInput,
} from './styledComponent';

function WritePost() {
    const [inputs,setInputs] = useState({
        title : '',
        contents :'',
    });
    const {title, contents} = inputs;

    const onChange = (e) => {
        const {value, name} = e.target;
        setInputs({
            ...inputs,
            [name] : value,
        });
    };
  return (
    <PostSection>
      <PostTitleDiv>
        <PostTitle>글쓰기</PostTitle>
      </PostTitleDiv>
      <PostWriteDiv>
        <TitleInput 
            name = "title"
            value = {title}
            placeholder="제목을 입력해주세요. (15자 이내)"
            onChange = {onChange}/>
        <ContentsInput cols="30" rows="10"></ContentsInput>
      </PostWriteDiv>
      <PostSubmitDiv>
        <PostSubmit>작성완료</PostSubmit>
      </PostSubmitDiv>
    </PostSection>
  );
}

export default WritePost;

여기서 주목해야 하는 코드는 다음과 같다.

const [inputs,setInputs] = useState({
        title : '',
        contents :'',
    });

두개의 값을 받아야 하므로 두 변수를 생성합니다. 그리고

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

이렇게 onChange함수를 작성하여, 각 value, name에 값을 매칭시킵니다. 기존에 inputs값을 복사해와 그 위에 덮어씌워서 input값을 저장해야 하므로 ...inputs가 존재하는 것입니다.

<TitleInput 
            name = "title"
            value = {title}
            placeholder="제목을 입력해주세요. (15자 이내)"
            onChange = {onChange}/>

그리고 제목을 쓰고 나면 이렇게 값이 저장될 수 있도록 합니다.

0개의 댓글