세현's 개발로그

[React] 컴포넌트 트리에 데이터 공급하기 (feat. 일기장) 본문

React

[React] 컴포넌트 트리에 데이터 공급하기 (feat. 일기장)

SarahPark 2023. 5. 7. 19:25

◈ Context

자식 컴포넌트에게 전달하기 위해 그냥 거쳐가는 props(props drilling)가 존재하는 문제를 해결하기 위해 Context를 사용한다.

형식은 다음과 같다.

 

App.js

1) export const DiaryStateContext = createContext(null);

 

data를 전역적으로 전달하기 위한 Context API를 만들어준다. 다른 컴포넌트들도 사용할 수 있도록 하기 위해 export를 써준다.

 

2) export const DiaryDispatchContext = createContext(null);

 

onCreate, onRemove, onEdit을 전역적으로 전달하기 위한 Context API를 만들어준다. 마찬가지로 export 해준다.

 

3) const memoizedDispatch = useMemo(() => {
    return { onCreate, onRemove, onEdit };
  }, []);

 

useMemo를 이용해 onCreate, onRemove, onEdit을 묶어준다. 이때 useMemo를 사용하지 않고 그냥 묶어준다면, App.js가 리랜더링 될 때마다 묶어준 것들도 리랜더링 되어 그동안 최적화 해줬던 것이 다 풀리게 된다.

 

4)   return (
    <DiaryStateContext.Provider value={store}>
      <DiaryDispatchContext.Provider value={memoizedDispatch}>
        <div className="App">
          <DiaryEditor />
          <div>전체 일기 : {data.length}</div>
          <div>기분 좋은 일기 개수 : {goodCount}</div>
          <div>기분 나쁜 일기 개수 : {badCount}</div>
          <div>기분 좋은 일기 비율 : {goodRatio}%</div>
          <DiaryList />
        </div>
 </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );

 

return 부분의 내용을 만들어줬던 Context API로 감싸준다. 바깥쪽은 state로 감싸주고, 안쪽은 dispatch로 감싸주게 된다.

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

export const DiaryStateContext = createContext(null);
export const DiaryDispatchContext = createContext(null);

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 () => {
    setTimeout(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 });
    }, 2000);
  };

  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;

  const store = {
    data
  };

  const memoizedDispatch = useMemo(() => {
    return { onCreate, onRemove, onEdit };
  }, []);

  return (
    <DiaryStateContext.Provider value={store}>
      <DiaryDispatchContext.Provider value={memoizedDispatch}>
        <div className="App">
          <DiaryEditor />
          <div>전체 일기 : {data.length}</div>
          <div>기분 좋은 일기 개수 : {goodCount}</div>
          <div>기분 나쁜 일기 개수 : {badCount}</div>
          <div>기분 좋은 일기 비율 : {goodRatio}%</div>
          <DiaryList />
        </div>
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );
};

export default App;

이렇게 최종적으로 일기장 프로젝트를 완성하게 되었다.

 

Comments