반응형
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

컴포넌트 트리에 데이터 공급하기 본문

인프런, 유데미/한입 크기로 잘라 먹는 리액트

컴포넌트 트리에 데이터 공급하기

5_hyun 2022. 6. 1. 13:29
반응형

전달만하는 prop가 많이 생기면 이름 바꾸기도 어렵고 코드 작성, 수정에 악영향을 끼친다.

이런 상황을 props가 드릴처럼 땅을 파고 들어간다고 보고 Prop Drilling이라고 한다.

=> 부모에서 자식으로만 데이터를 전달하는 단방향 데이터 흐름이라서 이런 문제가 생긴다.

 

1. 모든 데이터를 Provider라는 공격자 역할을 하는 컴포넌트에게 전달

2. Provider는 특별해서 자신의 자손들에게 직통으로 데이터를 줄 수 있다.

=> Prop Diilling이 없어졌다.

3. 자식 컴포넌트들은 Provider에게 직통으로 데이터를 받는다.

 

따라서 Provider 아래에 존재하는 모든 컴포넌트를 문맥화에 존재한다.

 

Context

-예시

const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Provider를 이용해 하위 트리에 테마 값을 보내줍니다.
    // 아무리 깊숙히 있어도, 모든 컴포넌트가 이 값을 읽을 수 있습니다.
    // 아래 예시에서는 dark를 현재 선택된 테마 값으로 보내고 있습니다.
    return (
      <ThemeContext.Provider value="dark">
        {/*이 Context안에 위치할 자식 컴포넌트들*/}
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

context는 인자로 defaultValue를 가지고 만들어지고 Context Provider를 통해 공급이 가능하다.

 

  • App.js
export const DiaryStateContext = React.createContext();

export default는 파일당 1개만 사용 가능해서 export만 한다. export하면 여러 개를 내보낼 수 있다.

 

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

이렇게 App의 return의 최상위 태그를 바꿔준다.

 

이렇게 Context.Provider를 보면 파란색으로 표시되는 부분이 문맥의 부분이다.

따라서 파란색 부분들은 Context.Provider가 공급하는 모든 데이터를 어디서든 가져다 쓸 수 있다.

 

-데이터 공급

  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 onEdit={onEdit} onRemove={onRemove} diaryList={data} />
      </div>
    </DiaryStateContext.Provider>
  );

value로 데이터를 공급하면 된다.

 

-DiaryList.js(자식 컴포넌트가 데이터 사용하기)

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

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} onEdit={onEdit} onRemove={onRemove} />
        ))}
      </div>
    </div>
  );
};

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

export default DiaryList;

diaryList를 이제 prop로 받을 필요없이 context에서 받아온다. 따라서 useContect를 사용하여 App.js에서 만든 DiaryStateContext를 받아온다.

 

그리고 보면 hooks의 Context에 일기 데이터들이 잘 전달된 것이 보인다.

 

다시 App.js에 돌아가서

 <DiaryStateContext.Provider value={data}>
      <div className="App">
        <DiaryEditor onCreate={onCreate} />
        <div>전체 일기 : {data.length}</div>
        <div>기분 좋은 일기 개수 : {goodCount}</div>
        <div>기분 나쁜 일기 개수 : {badCount}</div>
        <div>기분 좋은 일기 비율 : {goodRatio}</div>
        <DiaryList onEdit={onEdit} onRemove={onRemove} />
      </div>
    </DiaryStateContext.Provider>

DiaryList 컴포넌트에 diaryList를 prop로 전달하지 않아도 된다.

 

-상태 변화를 주도하는 함수도 Context로 공급하기

App.js에 DiaryStateContext.Provider의 value에 onCreate, onRemove, onEdit를 모두 넣어주면 될거같지만 이렇게하면 안된다. 왜냐하면 Provider도 컴포넌트라서이다. 그러면 Provider가 재생성되면 리렌더가 일어나고 결국 우리가 만들었던 최적화가 의미가 없어진다. 

=> 이럴땐 Context를 중첩으로 사용하면 된다.

 

App.js

export const DiaryDispatchContext = React.createContext();

따라서 이렇게 Context를 하나 더 만든다.

 

return (
    <DiaryStateContext.Provider value={data}>
      <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>
  );

그리고 중첩으로 이렇게 해준다.

그러면 onCreate, onRemove, onEdit은 재생성되지 않는 함수로만 이루어져있어서 재생성되지 않는다.

 

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

onCreate, onRemove, onEdit을 하나로 묶는다. 두번째 인자를 빈 배열로 한 이유는 절대 재생성되지 않게 하기 위해서이다.

useMemo를 활용하지 않으면 재생성되게된다.

 

<DiaryDispatchContext.Provider value={memoizedDispatch}>

그리고 DiaryDispatchContext.Provider의 value에 전달한다. 

<DiaryEditor />과 <DiaryList />는 더 이상 onCreate, onRemove, onEdit이 함수들을 전달 받을 필요가 없어져서 제거한다.

 

-DiaryEditor.js

onCreate prop를 제거한다.

  const { onCreate } = useContext(DiaryDispatchContext);

그리고 onCreate를 Context를 통해 비구조화 할당으로 가져온다.

 

-DiaryList.js

onRemove, onEdit를 제거한다.

 

그리고 Item에게 전달하는 Prop Drilling을 제거한다.

<DiaryItem key={it.id} {...it} />

 

-DiaryItem.js

onEdit, onRemove를 더 이상 prop로 받지 않게 한다.

 

  const { onRemove, onEdit } = useContext(DiaryDispatchContext);

그리고 Context로 부터 받는다.

반응형
Comments