Props Drilling 을 개선하기 위한 Context 사용법에 대해 간략히 정리한 글이다.
✔︎ Props Drilling 이란 ?
PropsDrilling이란 하위 컴포넌트가 상위 컴포넌트로부터 받은 props를 따로 사용하는게 아니라 자신의 다른 하위 컴포넌트에게 오로지 넘겨주기만 하는 데이터 전달 과정을 말한다.
위 화면은 React를 공부하면서 인프런 강의를 듣고 만들고 있는 간단한 react app 이다. App 컴포넌트에 하위 컴포넌트로 DiaryEditor 와 DiaryList 가 있고 DiaryList는 게시글 list를 props로 받아 하위컴포넌트인 DiaryItem으로 구성되어 있다.
위 구조를 도식화 해보면 아래와 같다.
위 그림에서 App 컴포넌트가 게시글 list(diaryList)를 state로 관리하고 있다고 가정해보자. DiaryEditor 컴포넌트는 onCreate 로 추가기존 list에 게시글을 추가하고 DiaryList 컴포넌트는 App컴포넌트로 부터 게시글 list(diaryList)를 받아 DiaryItem 컴포넌트로 List를 화면에 표출한다.
DiaryItem 컴포넌트를 보면 App 컴포넌트가 state로 관리하고 있는 특정 게시글을 수정하거나 삭제하는 기능이 있기 때문에 state를 변화시기기위에 onRemove와 onEdit을 App -> DiaryList -> DiaryItem 순으로 props를 받고 있다. DiaryList는 실제로 onRemove와 onEdit을 사용하지 않고 오직 DiaryItem에게 전달해주기만 하는데 지금은 컴포넌트의 숫자가 3개밖에 되지 않지만 아래로 10개, 20개씩 전달 과정이 늘어난다면 추후 관리하기에 props 추적이 힘들어질 것이다.
✔︎ Context 사용
위와 같은 상황을 해결하기 위해서 Context를 사용할 수 있다.
모든 데이터를 가지고 있는 App컴포넌트가 Provider 라는 공급자 역할을 하는 하위 컴포넌트에게 모든 데이터를 value로 넘겨주면 Provider 컴포넌트는 자신의 하위에 있는 모든 컴포넌트들에게 직통으로 데이터를 넘겨줄 수 있기 때문에 Props Drilling을 개선할 수 있다.
✔︎ Context 사용법
import "./App.css";
import DiaryEdtior from "./DiaryEditor";
import DiaryList from "./DiaryList";
import React, { useRef, useEffect, useMemo, useCallback, useReducer } from "react";
// ... 중략
// createContext로 context 생성하고 export
export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext();
const App = () => {
// ... 중략
const onCreate = (author, content, emotion) => {
dispatch({ type: "CREATE", data: { author, content, emotion, id: dataId.current } });
dataId.current += 1;
};
const onRemove = (targetId) => {
dispatch({ type: "REMOVE", targetId });
}
const onEdit = (targetId, newContent) => {
dispatch({ type: "EDIT", targetId, newContent });
}
const memoizedDispatches = useMemo(() => {
return { onCreate, onRemove, onEdit };
}, []);
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider value={memoizedDispatches}>
<div className="App">
<DiaryEdtior />
<DiaryList />
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
};
export default App;
우선 Provider 컴포넌트를 만들기위해서 React.createContex 를 사용하여 컴포넌트를 생성한다.
하위 컴포넌트로 전달할 다이어리 게시글 리스트 데이터(위 코드에서 state로 관리되는 data 변수에 해당)와, onCreate, onRemove, onEdit 함수를 Provider의 value로 전달을 할 것이다.
굳이 context를 두개 생성한 이유는 state와 함수를 같이 value로 묶어서 한개의 provider에 객체로 전달해주면 state 상태가 바뀔때마다 이 객체가 통째로 재생성되서 함수들도 다시 재생성 되기 때문이다. 이렇게 되면 게시글을 새로 작성하거나 수정하거나 삭제하여 state가 바뀔때마다 모든 게시글이 리렌더링이 일어날 것이다. 그런 현상을 방지하기 위해 context를 두개로 나누고 value로 전달되는 함수들은 useMemo를 사용하여 data state 값이 바껴도 DiaryDispatchContext.Provider 컴포넌트의 하위 컴포넌트는 리렌더링이 일어나지 않도록 하였다.(useContext로 사용되는 context 값이 바뀌면 해당 값을 사용하는 컴포넌트는 무조건 리렌더링이 발생함)
import React, { useContext } from "react";
import DiaryItem from "./DiaryItem";
import { DiaryStateContext } from "./App";
const DiaryList = () => {
const diaryList = useContext(DiaryStateContext);
return (
<div className="DiaryList">
<h2>Diary List</h2>
<h4>{diaryList.length}개의 글이 있습니다.</h4>
<div>
{diaryList.map((obj) => (
<DiaryItem key={obj.id} {...obj} />
))}
</div>
</div>
);
};
DiaryList.defaultProps = { diaryList: [] };
export default React.memo(DiaryList);
Provider 의 데이터를 하위 컴포넌트에서 받아 쓰기 위해서는 Provider 컴포넌트를 우선적으로 import를 하고 위와 같이 useContext를 사용하여 value를 가져다 쓰면 된다.