[React] 컴포넌트 트리에 데이터 공급 - Context

이재훈·2023년 6월 13일
0

React

목록 보기
20/27

리액트 컴포넌트 계층구조입니다. 현재 App컴포넌트에서 onEdit, onRemove 함수를 DiaryItem 컴포넌트에게 Props로 전달하고 있습니다. 여기서 문제는 DiaryList에는 onEdit, onRemove 함수를 사용하지 않지만 Props로 전달받고 다시 Props로 전달하는 "그냥 거쳐가는" 컴포넌트입니다. 딱봐도 비효율적입니다. 이것을 context로 사용하여 변경해보도록 하겠습니다.

모든 데이터를 가지고 있는 App 컴포넌트가 Provider 라는 데이터 공급 역할을 하는 컴포넌트에게 모든 데이터를 넘겨줍니다. Provider 컴포넌트는 특별한 컴포넌트입니다. 자식 컴포넌트에 해당하는 모든 컴포넌트들에게 직통으로 props를 전달할 수 있습니다.

react에서는 전역으로 관리해야할 데이터를 이 react context를 사용하여 관리할 수 있습니다.

Context 생성

const MyContext = React.createContext(defaultValue);

Context Provider를 통한 데이터 공급

<MyContext.Provider value={전역으로 전달하고자하는 값}>
  {/*이 context안에 위치할 자식 컴포넌트들*/} 
</MyContext.Provider>

App.js

// ... 생략
export const DiaryStateContext = React.createContext();

function App() {
// ... 생략

React.createContext 로 컴포넌트를 만들고 export 해줍니다.
export default는 js 파일 하나당 하나씩만 가능합니다. 하지만 그냥 export는 여러개가 가능합니다. 대신 import 할 때는 비구조화 할당으로만 import가 가능합니다.(같은 이름으로)

그리고 return문의 코드를 변경하겠습니다.

  return (
    <DiaryStateContext.Provider>
      <div className="App">
        <DiaryEditor onCreate={onCreate} />
        <div>전체 일기 : {data.length}</div>
        <div>배부른 일기 개수 : {notHungry}</div>
        <div>배고픈 일기 개수 : {reallyHungy}</div>
        <div>배부른 일기 비율 : {notHungryRatio}%</div>
        <div>배고픈 일기 비율 : {reallyHungryRatio}%</div>
        <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
      </div>
    </DiaryStateContext.Provider>
  );

DiaryStateContext.Provider 로 래핑 해주었습니다.

컴포넌트 트리 구조를 확인해보니 Context.Provider로 래핑이 잘 되어있는 것을 확인할 수 있습니다. 래핑되어진 컴포넌트들은 context provider가 공급하고 있는 모든 데이터를 가져다 사용이 가능합니다.

이제 데이터를 공급해보도록 하겠습니다.

const [data, dispatch] = useReducer(reducer, []);
// ... 생략
  return (
    <DiaryStateContext.Provider value={data}>
      <div className="App">
        <DiaryEditor onCreate={onCreate} />
        <div>전체 일기 : {data.length}</div>
        <div>배부른 일기 개수 : {notHungry}</div>
        <div>배고픈 일기 개수 : {reallyHungy}</div>
        <div>배부른 일기 비율 : {notHungryRatio}%</div>
        <div>배고픈 일기 비율 : {reallyHungryRatio}%</div>
        <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
      </div>
    </DiaryStateContext.Provider>
  );

DiaryStateContext.Provider에 value 속성을 사용하여 data를 전달하였습니다.

일기 데이터가 prop으로 전달된 것을 확인할 수 있습니다. 이제 이 값은 어디서든 사용이 가능합니다. 이제 이 데이터를 사용해보도록 하겠습니다.

기존의 DiaryList.js

import DiaryItem from "./DiaryItem";

const DiaryList = ({ onEdit, onRemove, diaryList }) => {
  return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 존재합니다.</h4>
      <div>
        {diaryList.map((it) => (
          <DiaryItem key={it.id} {...it} onRemove={onRemove} onEdit={onEdit} />
        ))}
      </div>
    </div>
  );
};

DiaryList.defaultProps = {
  diaryList: [],
};

export default DiaryList;

기존의 DiaryList 컴포넌트는 App 컴포넌트로부터 prop으로 전달받아 diaryList를 사용하고 있습니다.

변경한 DiaryList.js

import { useContext } from "react";
import DiaryItem from "./DiaryItem";
import { DiaryStateContext } from "./App";

const DiaryList = ({ onEdit, onRemove }) => {
  const diaryList = useContext(DiaryStateContext);
  return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 존재합니다.</h4>
      <div>
        {diaryList.map((it) => (
          <DiaryItem key={it.id} {...it} onRemove={onRemove} onEdit={onEdit} />
        ))}
      </div>
    </div>
  );
};

DiaryList.defaultProps = {
  diaryList: [],
};

export default DiaryList;

useContext react hooks를 사용하여 export된 DiaryStateContext를 diarylist에 담았습니다. 나머지 코드는 변경할 필요 없습니다.

일기 데이터도 잘 적용되어 있는것을 확인할 수 있습니다.

이제 props 드릴링을 하고 있던 onEdit()과 onRemove()도 적용해보도록 하겠습니다.

데이터와 함수는 별개의 컨텍스트를 만듭니다.

export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext(); // 추가 생성

App 컴포넌트가 리렌더링 되더라도 onCreate, onEdit, onRemove는 재생성됩니다. 전달되는 함수가 재생성되지 않게 useMemo를 사용해줍니다.(최적화가 풀리지 않게)

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

두번째 인자로 빈 배열을 넣어 재생성되지 않도록 합니다.

  return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider value={memoizedDispatches}>
        <div className="App">
          <DiaryEditor onCreate={onCreate} />
          <div>전체 일기 : {data.length}</div>
          <div>배부른 일기 개수 : {notHungry}</div>
          <div>배고픈 일기 개수 : {reallyHungy}</div>
          <div>배부른 일기 비율 : {notHungryRatio}%</div>
          <div>배고픈 일기 비율 : {reallyHungryRatio}%</div>
          <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
        </div>
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );

이제 memoizedDispatches를 value로 공급을 해줍니다. 결과적으로 onCreate, onEdit, onRemove를 props으로 직접 넘길 필요가 없어졌습니다.

  return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider value={memoizedDispatches}>
        <div className="App">
          <DiaryEditor />
          <div>전체 일기 : {data.length}</div>
          <div>배부른 일기 개수 : {notHungry}</div>
          <div>배고픈 일기 개수 : {reallyHungy}</div>
          <div>배부른 일기 비율 : {notHungryRatio}%</div>
          <div>배고픈 일기 비율 : {reallyHungryRatio}%</div>
          <DiaryList />
        </div>
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );

변경된 DiaryEditor.js

import React, { useContext, useEffect, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";

const DiaryEditor = () => {
  const { onCreate } = useContext(DiaryDispatchContext);

기존에 prop으로 onCreate를 전달 받았었습니다. 이제 useContext를 사용하여 onCreate를 비구조화 할당으로 함수를 가져왔습니다.

변경된 DiaryEditor.js

import React, { useContext, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";

const DiaryItem = ({ name, content, created_date, hungry, id }) => {
  const { onRemove, onEdit } = useContext(DiaryDispatchContext);
  // ... 생략

변경된 DiaryList.js

import { useContext } from "react";
import DiaryItem from "./DiaryItem";
import { DiaryStateContext } from "./App";

const DiaryList = () => {
  const diaryList = useContext(DiaryStateContext);

  return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 존재합니다.</h4>
      <div>
        {diaryList.map((it) => (
          <DiaryItem key={it.id} {...it} />
        ))}
      </div>
    </div>
  );
};

DiaryList.defaultProps = {
  diaryList: [],
};

export default DiaryList;

props 드릴링을 드디어 지웠습니다..!


작동도 잘 되는 것을 확인하였습니다.

react context api를 사용하여 props 드릴링을 없앴습니다.


리액트 공식 홈페이지
https://ko.legacy.reactjs.org/docs/react-api.html#reactmemo
해당 게시글은 인프런 강의
"한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지(이정환)"
를 정리한 내용입니다. 쉽게 잘 설명해주시니 여러분도 강의를 듣는 것을 추천드립니다.

profile
부족함을 인정하고 노력하자

0개의 댓글