본문 바로가기
React/Practice

React-redux를 사용한 게시판 만들기 with Example code

by 3.dev 2023. 1. 29.
반응형

난이도

(실습 난이도 : 하, 소요시간 : 30min±)

실습 설명

오늘은 React-redux를 사용해서 게시판을 만들어볼 것이다.

사용할 컴포넌트는 기본으로 제공되는 App.jsx와
Post, Add, Find, Store를 새로 생성해서 사용할 것이다.

실습

먼저 컴포넌트를 생성하고 App.jsx에 import해주자.
하는김에 React-redux를 사용 할 것이기 때문에 store생성, Provider도 같이 감싸주겠다.

[App.jsx]
import { Provider } from "react-redux";
import { createStore } from "redux";
import "./App.css";
import Add from "./components/Add";
import Post from "./components/Post";
import Find from "./components/Find";
import reducer from "./Store";

const store = createStore(reducer);

export default function App() {
  return (
    <div className="App">
      <Provider store={store}>
        <Find />
        <Post />
        <Add />
      </Provider>
    </div>
  );
}

가장먼저 Post컴포넌트에서 글목록이 보일 수 있게 해보자.

[Post.jsx]
export default function Post() {

  const posts = [
    {
      title: "리액트",
      writer: "홍길동",
    },
    {
      title: "리덕스",
      writer: "홍길동",
    },
    {
      title: "자바스크립트",
      writer: "홍길동",
    },
  ]
  
  return (
      <div>
        <ol>
          {posts.map((post, idx) => (
            <li key={idx}>
              <span>
                {post.title}-{post.writer}
              </span>
              <button>
                글삭제
              </button>
            </li>
          ))}
        </ol>
      </div>
  );
}

자 이제 게시글을 검색할 수 있는 Find UI와 Post UI를 만들어보자.

[Find.jsx]
import { useState } from "react";

export default function Find() {
  const [postName, setPostName] = useState("");

  const formEvent = (e) => {
    e.preventDefault();
  };

  return (
    <form onSubmit={formEvent}>
      <input
        type="text"
        placeholder="검색"
        value={postName}
        onChange={(e) => setPostName(e.target.value)}
      />
      <input type="submit" value="검색" />
    </form>
  );
}
[Add.jsx]
import { useState } from "react";

export default function Add() {
  const [postAddData, setPostAddData] = useState({
    title: "",
    writer: "",
  });

  const changeValue = (e) => {
    const { name, value } = e.target;
    const newValue = { ...postAddData, [name]: value };

    setPostAddData(newValue);
  };

  return (
    <div>
      <div>
        <input
          type="text"
          placeholder="제목"
          name="title"
          value={postAddData.title}
          onChange={changeValue}
        />
        <input
          type="text"
          placeholder="글쓴이"
          name="writer"
          value={postAddData.writer}
          onChange={changeValue}
        />
      </div>
    </div>
  );
}

그럼 이제 Store를 만들어서 각 컴포넌트에서 요청하는 것을 처리하고,
원하는 자료를 건네줄 수 있는 코드를 작성해보자.

[store.jsx]
export default function reducer(currentState, action) {
  if (currentState === undefined) {
    return {
      posts: [
        {
          title: "리액트",
          writer: "홍길동",
        },
        {
          title: "리덕스",
          writer: "홍길동",
        },
        {
          title: "자바스크립트",
          writer: "홍길동",
        },
      ],
      search: {},
    };
  }

  const newState = { ...currentState };

  const postMap = {
    POSTADD() {
      newState.posts.push(action.data);
      newState.search = {};
    },
    POSTREMOVE() {
      newState.posts.splice(action.idx, 1);
    },
    SEARCH() {
      if (
        newState.posts.filter((posts) => posts.title === action.data)[0] !==
        undefined
      ) {
        newState.search = newState.posts.filter(
          (posts) => posts.title === action.data
        )[0];
      } else {
        alert("검색결과가 없습니다.\n글제목을 정확히 입력해주세요.");
      }
    },
  };

  postMap[action.type]();

  return newState;
}

우리가 배웠던 if/else문법에서 객체/함수형으로 바꿔보았다.
물론 if/else가 한번 들어가긴 했지만 그 전 if/else보다는 효율적이고
유지보수 측면에서 postMap에서만 수정,추가,삭제하면 된다는 점에서
훨씬 나은 코드라고 생각한다.

그럼 글목록도 Store에서 받아오고, Find, Add, Remove를 Store를 통해서
할수 있도록 코딩해보자.

추가로 게시글과 공개여부를 더 넣어주었다.

[Post.jsx]
import { useDispatch, useSelector } from "react-redux";

export default function Post() {
  const state = useSelector((state) => state);
  const dispatch = useDispatch();

  return (
    <>
      <div>
        <ol>
          {state.posts.map((post, idx) => (
            <li key={idx}>
              <span>
                {post.title}-{post.writer}
              </span>
              <button
                onClick={() => {
                  dispatch({ type: "POSTREMOVE", idx: idx });
                }}
              >
                글삭제
              </button>
            </li>
          ))}
        </ol>
      </div>
      {state.search.title !== undefined && (
        <div>
          <div>
            제목 - {state.search.title} / 글쓴이 - {state.search.writer}
          </div>
          <div>
            {state.search.secret
              ? "로그인 후 확인 가능합니다."
              : state.search.desc}
          </div>
        </div>
      )}
    </>
  );
}

앞서 작성했던 것에 사용했던 객체선언을 없애고 Store에서 가져올 수 있도록 State를 선언.
글삭제 버튼을 통해 Store에서 선택한 글 번호를 기준으로 삭제할 수 있도록 했다.

다음은 글 작성이다.

[Add.jsx]
import { useState } from "react";
import { useDispatch } from "react-redux";

export default function Add() {
  const [postAddData, setPostAddData] = useState({
    title: "",
    writer: "",
    secret: true,
    desc: "",
  });

  const dispatch = useDispatch();

  const changeValue = (e) => {
    const { name, value } = e.target;
    const newValue = { ...postAddData, [name]: value };

    setPostAddData(newValue);
  };

  return (
    <div>
      <div>
        <input
          type="text"
          placeholder="제목"
          name="title"
          value={postAddData.title}
          onChange={changeValue}
        />
        <input
          type="text"
          placeholder="글쓴이"
          name="writer"
          value={postAddData.writer}
          onChange={changeValue}
        />
        <span>공개여부</span>
        <input
          type="checkbox"
          name="secret"
          value={postAddData.secret}
          onChange={() => {
            setPostAddData({
              ...postAddData,
              secret: !postAddData.secret,
            });
          }}
        />
      </div>
      <textarea
        name="desc"
        placeholder="내용을 입력해주세요"
        cols="50"
        rows="10"
        value={postAddData.desc}
        onChange={changeValue}
      />
      <button
        onClick={() => {
          dispatch({ type: "POSTADD", data: postAddData });
          setPostAddData({ title: "", writer: "", secret: false, desc: "" });
        }}
      >
        등록
      </button>
    </div>
  );
}

작성또한 버튼을 누르면 작성했던 제목,글쓴이,공개여부,글내용을 Store에 게시할 수 있도록 했다.
여기서 느껴지는게 코드 몇줄 수정안했다.
React-redux는 진짜 편한 것 같다.

이어서 Find도 보자.

[Find.jsx]
import { useState } from "react";
import { useDispatch } from "react-redux";

export default function Find() {
  const dispatch = useDispatch();
  const [postName, setPostName] = useState("");

  const formEvent = (e) => {
    e.preventDefault();
    dispatch({ type: "SEARCH", data: postName });
  };

  return (
    <form onSubmit={formEvent}>
      <input
        type="text"
        placeholder="검색"
        value={postName}
        onChange={(e) => setPostName(e.target.value)}
      />
      <input type="submit" value="검색" />
    </form>
  );
}

SEARCH라는 타입으로 전송하고, 글제목을 전송해 같은 게시물을 찾아주는 동작이다.
그럼 마지막으로 Store와, Post를 조금 가다듬고 결과를 보자.

최종코드

[store.jsx]
export default function reducer(currentState, action) {
  if (currentState === undefined) {
    return {
      posts: [
        {
          title: "리액트",
          writer: "홍길동",
          secret: true,
          desc: "리액트란? ...",
        },
        {
          title: "리덕스",
          writer: "홍길동",
          secret: true,
          desc: "리덕스란? ...",
        },
        {
          title: "자바스크립트",
          writer: "홍길동",
          secret: false,
          desc: "자바스크립트란? ...",
        },
      ],
      search: {},
    };
  }

  const newState = { ...currentState };

  const postMap = {
    POSTADD() {
      newState.posts.push(action.data);
      newState.search = {};
    },
    POSTREMOVE() {
      newState.posts.splice(action.idx, 1);
    },
    SEARCH() {
      if (
        newState.posts.filter((posts) => posts.title === action.data)[0] !==
        undefined
      ) {
        newState.search = newState.posts.filter(
          (posts) => posts.title === action.data
        )[0];
      } else {
        alert("검색결과가 없습니다.\n글제목을 정확히 입력해주세요.");
      }
    },
  };

  postMap[action.type]();

  return newState;
}
[Post.jsx]
import { useDispatch, useSelector } from "react-redux";

export default function Post() {
  const state = useSelector((state) => state);
  const dispatch = useDispatch();

  return (
    <>
      <div>
        <ol>
          {state.posts.map((post, idx) => (
            <li key={idx}>
              <span>
                {post.title}-{post.writer}
              </span>
              <button
                onClick={() => {
                  dispatch({ type: "POSTREMOVE", idx: idx });
                }}
              >
                글삭제
              </button>
            </li>
          ))}
        </ol>
      </div>
      {state.search.title !== undefined && (
        <div>
          <div>
            제목 - {state.search.title} / 글쓴이 - {state.search.writer}
          </div>
          <div>
            {state.search.secret
              ? "로그인 후 확인 가능합니다."
              : state.search.desc}
          </div>
        </div>
      )}
    </>
  );
}

결과

손쉽게 가짜게시판을 만들 수 있었다.
물론 진짜 게시판은 서버로 전달하고 서버에서 받아오겠지만
React-redux를 연습하기에는 너무 좋은 것 같다.

반응형