App컴포넌트에서 모든 데이터를 Provider 컴포넌트에 내려주면
Provider 컴포넌트는 그 데이터를 자식 컴포넌트에게 다이렉트로 전달해줄 수 있습니다.
Provider컴포넌트의 자식 컴포넌트들은 Context 하에 존재합니다.
(Redux의 스토어 Provider 같은 느낌)
Context API를 사용하면 불필요한 Props drilling을 피할 수 있습니다.
Context 생성
const Mycontext = React.createContext(defaultValue);
Context Provider를 통한 데이터 공급
<MyContext.Provider value={전역으로 전달하고자 하는 값}>
{//이 context 안에 위치할 자식 컴포넌트들}
</MyContext.Provider>
App.js
createContext를 통해 만든 컨텍스트에 접근하여 데이터를 꺼내와야 하기 때문에 export로 내보내줍시다.
//해당 context도 es모듈 시스템으로 내보내줘야 다른 곳에서 사용할 수 있기 때문에 export 해준다.
export const DiaryStateContext = React.createContext();
//중략, App 컴포넌트의 return 부에 만들어둔 context를 제공한다.
return (
<DiaryStateContext.Provider value={data}>
<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>
</DiaryStateContext.Provider>
);
DiaryList 에서 불러와서 사용한다면,
src/components/DiaryList.js
import React, { useContext } from "react";
import DiaryItem from "./DiaryItem";
import { DiaryStateContext } from "../App";
function DiaryList({ onRemove, onEdit }) {
// useContext의 인자로 만들어둔 컨텍스트 컴포넌트를 넣어줘야 한다.
// export해놓은 context 컴포넌트를 임포트해오고 사용
const diaryList = useContext(DiaryStateContext);
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<b>총 {diaryList.length}개의 일기가 있습니다.</b>
<div>
<ul>
{diaryList.map((item) => {
return (
<DiaryItem
key={item.id}
{...item}
onRemove={onRemove}
onEdit={onEdit}
/>
);
})}
</ul>
</div>
</div>
);
}
React Development tool로 확인해보면 아래와 같은 결과를 확인할 수 있습니다.
우리가 props로 넘겨준 것들 중 이제 값이 아닌 함수들이 남았는데요. 얘네는 또 다른 context를 만들어줘야 합니다.
이유는 DiaryStateContext.Provider도 컴포넌트이기 때문에 value로 넘겨주는 값을 여러 개 넣어주게 되면 이 value값이 변할때마다 해당 Provider도 렌더링이 일어나고 하위 컴포넌트들도 리렌더링이 일어나게 되기 때문입니다.
App.js
export const DiaryDispatchContext = React.createContext();
useMemo를 사용하여 렌더링이 일어나지 않도록 만들어둔 함수명들을 하나로 묶어 줍니다. 새로운 context에 value값으로 넘겨줘야 하기 때문이죵(비구조화 할당을 통해 뽑아쓸거니까 {함수명, 함수명, 함수명}) useMemo를 사용하지 않으면 애써 각각의 함수에 useCallback으로 해놨던 최적화가 의미없게 됩니다.
App.js
//useMemo를 사용하지않고 그냥 객체로 묶어주게되면 컴포넌트가 재생성될 때 같이 다시 만들어지게 된다.
//그렇기 때문에 useMemo를 통해 묶어줘야 한다.
const memoizedDispatches = useMemo(() => {
return { onCreate, onRemove, onEdit };
}, []);
onCreate, onEdit, onRemove 함수를 하나로 묶어서 그 데이터를 value값으로 넘겨줍니다.
결론적으로 Provider가 중첩된 모습으로 아래와 같이 나타나게 됩니다.
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider value={memoizedDispatches}>
<div className="App">
<DiaryEditor />
<div>전체 일기: {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 안좋은 일기 개수: {badCount}</div>
<div>기분 좋은 일기 퍼센트: {goodRatio}%</div>
<DiaryList />
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
제대로 작동하는 지 확인하려면 리액트 디벨롭먼트 툴을 사용하면 됩니다.
해당 값을 받아와 쓸 곳에서 Provider컴포가 제공하는 value를 가져와 봅시다.
src/components/DiaryEditor.js
import React, { useState, useRef, useEffect, useContext } from "react";
import { DiaryDispatchContext } from "./App";
const { onCreate } = useContext(DiaryDispatchContext);
onRemove와 onEdit을 prop으로 받던 DiaryItem에서도 context를 통해 value를 가져와봅시다.
src/components/DiaryItem.js
import React, { useState, useRef, useEffect, useContext } from "react";
import { DiaryDispatchContext } from "../App";
function DiaryItem({ id, author, contents, emotion, created_date }) {
const { onRemove, onEdit } = useContext(DiaryDispatchContext);
//코드 중략
함수를 전달해주는 중간다리 역할을 하던 컴포넌트에서 prop으로 넘겨주는 부분을 지워주면
프롭 드릴링없이 context를 통해 다이렉트로 state를 value로 받아 사용할 수 있었습니다!
😎 context를 한 파일에서 생성할 때 개수 제한은 없지만 컴포넌트 간 공유해야하는 데이터가 많을 경우는 redux와 같은 상태관리 라이브러리를 사용하는 것이 더 나을 거 같습니다.