세현's 개발로그

[React] 복잡한 상태 관리 로직 분리하기 - useReducer (feat. 일기장) 본문

React

[React] 복잡한 상태 관리 로직 분리하기 - useReducer (feat. 일기장)

SarahPark 2023. 5. 7. 18:23

◈ useReducer

지금까지 작성한 App.js 코드는 useState가 너무 많아 컴포넌트 내의 내용이 너무 복잡하다는 문제가 있다.

이를 해결하기 위해 useReducer를 사용하여 상태변화로직을 App.js컴포넌트로부터 분리한다. 

 

useReducer는 const [data, dispatch] = useReducer(reducer, []); 

이러한 형식을 사용하는데 이때 배열의 비구조화할당에서 0번째 인자는 항상 state이고, 두 번째 인자는 dispatch이다. 

useReducer의 첫 번째 인자는 reducer(상태변화처리함수)이고 두 번째 인자는 state의 초기값이다.

상태변화처리함수인 reducer는 컴포넌트 밖으로 분리하여 직접 만들어줘야 한다.

 

const reducer = (state, action) =>

{ switch( action.type) {

reducer는 두 개의 파라미터를 가지는데,

첫 번째 파라미터는 상태변화가 일어나기 직전의 현재상태, 두 번째 파라미터는 상태변화에 대한 정보가 들어있는 action 객체이다. 기존에 setData가 했었던 역할을 dispatch와 reducer가 나눠서 수행하도록 만들어준다.

App.js

import React, {
  useCallback,
  useEffect,
  useMemo,
  useReducer,
  useRef
} from "react";
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
import "./App.css";

const reducer = (state, action) => {
  switch (action.type) {
    case "INIT": {
      return action.data;
    }
    case "CREATE": {
      const created_date = new Date().getTime();
      const newItem = {
        ...action.data,
        created_date
      };
      return [newItem, ...state];
    }
    case "REMOVE": {
      return state.filter((it) => it.id !== action.targetId);
    }
    case "EDIT": {
      return state.map((it) =>
        it.id === action.targetId
          ? {
            ...it,
            content: action.newContent
          }
          : it
      );
    }
    default:
      return state;
  }
};

const App = () => {
  const [data, dispatch] = useReducer(reducer, []);
  const dataId = useRef(0);
  const getData = async () => {
    const res = await fetch(
      "https://jsonplaceholder.typicode.com/comments"
    ).then((res) => res.json());

    const initData = res.slice(0, 20).map((it) => {
      return {
        author: it.email,
        content: it.body,
        emotion: Math.floor(Math.random() * 5) + 1,
        created_date: new Date().getTime(),
        id: dataId.current++
      };
    });

    dispatch({ type: "INIT", data: initData });
  };

  useEffect(() => {
    getData();
  }, []);

  const onCreate = useCallback((author, content, emotion) => {
    dispatch({
      type: "CREATE",
      data: { author, content, emotion, id: dataId.current }
    });
    dataId.current += 1;
  }, []);

  const onRemove = useCallback((targetId) => {
    dispatch({ type: "REMOVE", targetId });
  }, []);

  const onEdit = useCallback((targetId, newContent) => {
    dispatch({
      type: "EDIT",
      targetId,
      newContent
    });
  }, []);

  const memoizedDiaryAnalysis = useMemo(() => {
    const goodCount = data.filter((it) => it.emotion >= 3).length;
    const badCount = data.length - goodCount;
    const goodRatio = (goodCount / data.length) * 100.0;
    return { goodCount, badCount, goodRatio };
  }, [data.length]);

  const { goodCount, badCount, goodRatio } = memoizedDiaryAnalysis;

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}%</div>
      <DiaryList diaryList={data} onRemove={onRemove} onEdit={onEdit} />
    </div>
  );
};

export default App;
Comments