context API : 컴포넌트 트리에 데이터 공급

조뮁·2022년 11월 27일

React

목록 보기
20/34

현재 컴포넌트 계층구조

  • DiaryList 컴포넌트는 diaryList, onRemove(), onEdit() 프롭스를 받지만, 실제 사용하는건 diaryList 프롭스뿐임

Prop Drilling

: props를 오로지 하위 컴포넌트로 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서 React Component 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정

  • 많은 컴포넌트를 거칠수록 해당 prop의 추적과 유지보수가 힘들어짐

context

: Provider와 Provider가 공급하는 데이터를 받을수 있는 컴포넌트들의 영역

  1. Provider(공급자 역할을 하는 자식 컴포넌트)에게 모든 데이터를 넘겨줌
  2. Provider는 자신의 자손 컴포넌트에 직접 데이터를 전달할 수 있음 = props drilling이 없음
  3. Provider의 자식 컴포넌트들은 모두 Provider 컴포넌트에게 직접 데이터를 공급받음

단, Provider컴포넌트의 자손이 아닌 컴포넌트는 Provider 컴포넌트가 공급하는 데이터를 받을 수 없음. 같은 문맥(context)이 아님.

Context API

// Context 생성
const MyContext = React.createContext(defaultValue);

// Context Provider를 통한 데이터 공급
<MyContext.Provider value={전역으로 전달하고자 하는 값}>
  {/* 해당 Context안에 위치할 자식 컴포넌트들 */}
</MyContext.Provider>
  • Provider : value라는 props를 받아 자식 컴포넌트들에게 전달
  • 값을 전달받을 수 있는 컴포넌트 개수 제한 X

Context API 사용

Context 생성 및 Provider에 데이터 공급

  1. Context 생성하여 export 해주기
// Context API를 이용해서 data state를 전역으로 공급할 Context 생성
// Context도 export해줘야 다른 컴포넌트들이 Context에 접근하여 Provider가 공급하는 데이터를 받아올 수 있음.
export const DiaryStateContext = React.createContext();
  1. Context Provider 생성
  • App()의 return 영역의 최상의 태그를 해당 Context.Provider로 변경
return (
    <DiaryStateContext.Provider>
      <div className="App">
        <OptimizeTest />
        {/* <Lifecycle /> */}
        <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>
  );

  • Context.Provider가 DiaryEditor와 DiaryList의 상위에 있음 -> Context하위에 있는 컴포넌트(DiaryEditor, DiaryList)는 Provider가 공급하는 데이터를 가져다가 쓸 수 있음
  1. Provider에 데이터 공급
  • Provider의 value에 prop으로 data를 전달
return (
    <DiaryStateContext.Provider value={data}>
      <div className="App">
        <OptimizeTest />
        {/* <Lifecycle /> */}
        <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>
  );

Context 하위 컴포넌트에서 데이터 사용

  1. DiaryList
import { useContext } from "react";
import { DiaryStateContext } from "./App"; // App에서 export한 Context를 비구조화할당을 통해 import 받아와서 사용해야함
import DiaryItem from "./DiaryItem";

// diaryList 데이터는 더 이상 App()에서 prop으로 받아올 필요가 없음
const DiaryList = ({ onRemove, onEdit }) => {
  // Context에서 데이터를 받기 위해서 useContext Hook 사용
  const diaryList = useContext(DiaryStateContext);

  diaryList.map((lst) => {});
  return (
    <div className="DiaryList">
      <h2>일기 리스트 ({diaryList.length})</h2>
      <div>
        {diaryList.map((lst, idx) => (
          <DiaryItem
            key={lst.id}
            {...lst}
            onRemove={onRemove}
            onEdit={onEdit}
          />
        ))}
      </div>
    </div>
  );
};

  • Context라고 되어있는 값이 일기데이터임
  • useContext 기능을 통해 특정 Context(DiaryStateContext)를 지정하여 그 Context에서 원하는 데이터(prop)을 받아올 수 있음
// App.js
return (
    <DiaryStateContext.Provider value={data}>
      <div className="App">
        <OptimizeTest />
        {/* <Lifecycle /> */}
        <DiaryEditor onCreate={onCreate} />
        <div>전체 일기 : {data.length}</div>
        <div>기분 좋은 일기 개수 : {goodCount}</div>
        <div>기분 나쁜 일기 개수 : {badCount}</div>
        <div>기분 좋은 일기 비율 : {goodRatio}</div>
// DiaryList로 전달해주던 diaryList prop 삭제
        <DiaryList onRemove={onRemove} onEdit={onEdit} />
      </div>
    </DiaryStateContext.Provider>
  );
  • DiaryList는 diaryList 데이터를 Context에서 꺼내오면 되기 때문에, App.js에서 prop으로 전달해줄 필요 없음

data drilling 제거

<DiaryStateContext.Provider value={data} />에 onCreate(), onReve(), onEdit()을 props로 모두 전달하면 안됨!
-> Provider도 컴포넌트이기 때문

  • prop이 바뀌어버리면 컴포넌트가 재생성됨 -> Provider 하위에 있는 컴포넌트들도 모두 재생성됨

  • Provider에 data상태값 변경 함수들을 prop으로 내려주게 되면, data state가 바뀔때마다 리랜더링이 일어남

  • 새로운 context를 생성하여 data를 전달하는 context와 data 상태 변경 함수를 전달하는 context를 분리해서 사용하면 됨

  • 새로운 context 생성

// App.js
// state 변화 함수들을 공급할 context 생성
export const DiaryDispatchContext = React.createContext();

// onCreate(), onEdit(), onRemove()함수를 하나로 묶어서 전달
  // memoizedDispatchFunc가 재생성되지 않도록 useMemo의 depth를 빈배열로 전달
  const memoizedDispatchFunc = useMemo(() => {
    return { onCreate, onRemove, onEdit };
  }, []);

// 새롭게 만든 DiaryDispatchContext.Provider를  DiaryStateContext.Provider 바로 하단 자식요소로 추가
return (
    <DiaryStateContext.Provider value={data}>
  	  // dispatch 함수들을 prop으로 전달
      <DiaryDispatchContext.Provider value={memoizedDispatchFunc}>
        <div className="App">
          <OptimizeTest />
          {/* <Lifecycle /> */}
          <DiaryEditor /> {/* prop으로 전달하던 dispatch 함수 제거 */}
          <div>전체 일기 : {data.length}</div>
          <div>기분 좋은 일기 개수 : {goodCount}</div>
          <div>기분 나쁜 일기 개수 : {badCount}</div>
          <div>기분 좋은 일기 비율 : {goodRatio}</div>
          <DiaryList /> {/* prop으로 전달하던 dispatch 함수 제거 */}
        </div>
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );

useMemo를 활용하여 dispatch 함수들을 묶는 이유

const dispatchesFunc = {
	onCreate, onRemove, onEdit
}

과 같이 객체로 만들면 App컴퍼넌트가 재생성될 때 dispatchesFunc 객체도 재생성되기 때문

  • onCreate, onEdit, onRemove가 Provider로 전달됨.

함수 전달받기

  • 다이어리 추가
// DiaryEditor.js
import React, { useContext, useEffect, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";

const DiaryEditor = () => {
  // onCreate props 받기  (DiaryStateContext에서는 받아올 수 없음)
  // DiaryDispatchContext 안에서 객체 형태로 함수가 전달되기 때문에 비구조화 할당으로 받아와야함
  const { onCreate } = useContext(DiaryDispatchContext);
  • 다이어리 수정, 삭제
// DiaryList.js
const DiaryList = () => {
  /* Props로 전달하던 onEdit, onRemove 제거 */
  const diaryList = useContext(DiaryStateContext);

  diaryList.map((lst) => {});
  return (
    <div className="DiaryList">
      <h2>일기 리스트 ({diaryList.length})</h2>
      <div>
        {diaryList.map((lst, idx) => (
          <DiaryItem key={lst.id} {...lst} />
        ))}
      </div>
    </div>
  );
};
// diaryItem.js
import React, { useContext, useEffect, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";

const DiaryItem = ({
  /* Props로 받던 onEdit, onRemove 삭제 */
  author,
  content,
  emotion,
  created_date,
  id,
}) => {
  const { onRemove, onEdit } = useContext(DiaryDispatchContext);

export default /// export 차이

Context export 시, export default로 사용 X

  • export default는 파일 하나당 한 번만 쓸 수 있음
  • export만 사용하면 여러개 사용 가능
import React, {
  useEffect,
  useMemo,
  useRef,
  useCallback,
  useReducer,
} from "react";
  • React는 그냥 import하는데, 나머지 기능들은 {}안에서 비구조화 할당을 통해 import 받고 있음
  • 비구조화 할당을 통해 받는 기능들은 import 시 이름을 바꿀 수 없지만, React는 이름을 변경하여 import 할 수 있음
  • from "react" 의 react파일에서 export default로 내보내졌으면 이름을 바꿔서 (React) 받아올 수 있고, 그냥 export로 내보내진 컴포넌트는 그 이름을 비구조화 할당을 통해서만 받아올 수 있음
  • App.js에서는 기본적으로 App() 컴포넌트를 내보내고 있고, 부가적으로 DiaryStateContext같은 컴포넌트(?)를 내보내고 있는 것.

0개의 댓글