[Udemy] React 기본 - 일기장 만들기(14) 컴포넌트 트리에 데이터 공급하기 : Context

productuidev·2022년 5월 8일
0

React Study

목록 보기
33/52
post-thumbnail

React 기본 (Project)

Udemy - 한입크기로 잘라 먹는 리액트


📌 일기장 만들기 (14)

☑️ Context API란?

  • 리액트의 컴포넌트 트리에 전형적으로 데이터를 공급
  • 지금까지 만들어 본 컴포넌트 계층 구조 살펴보기

  • 실제로 사용하는 Prop과 그냥 거쳐가는 Prop이 존재
  • 전달만 하는 컴포넌트가 중간에 많이 생기게 될 경우 함수 이름도 바꾸기 어렵고, 코드 작성이나 수정에 상당한 악영향을 끼치게 됨
  • Props Drilling 문제 해결 : 부모에서 자식으로만 데이터를 전달하는 단방향 데이트 흐름을 가진 리액트 특성상 발생하게 문제를 해결하기 위해 나온 Context

☑️ Provider

  • 모든 데이터를 가지고 있는 컴포넌트가 Provider라는 공급자 역할을 하는 자식 컴포넌트에게 자신이 가지고 있는 모든 데이터를 준다
  • 공급자인 Provider 컴포넌트는 자신의 자손에 해당하는 모든 컴포넌트들에게 직통으로 데이터를 줄 수가 있다 (Props Drilling 제거)
  • 자식 컴포넌트들은 공급자 Provider 컴포넌트에게 직통으로 데이터를 공급받을 수 있다. 이렇게 되면 Props만 전달하는 컴포넌트도 필요 없고 Props를 전달하는 코드들도 사라지게 되어 코드 가독성이 좋아진다.
  • Context의 사전적 의미 : 문맥, 맥락, 글의 흘러가는 방향
  • Provider 컴포넌트 하위에 있는 모든 컴포넌트들은 일기 데이터를 조작하고 관리하는 문맥 아래에 존재하게 된다라고 이해할 수 있다. 쉽게 말해 하위 컴포넌트들은 일기 데이터를 관리하기 위한 문맥 속에서 살아간다는 의미가 된다.

  • Provider 컴포넌트의 자손으로 배치되지 않은 컴포넌트(Counter)는 Provider 컴포넌트가 공급하는 데이터에 접근할 수 없으며 결론적으로 같은 Context가 아니라고 할 수 있다.

☑️ 이용방법

☑️ DiaryStateContext

src/App.js

import React, { useCallback, useEffect, useMemo, useRef, useReducer } from "react";

export const DiaryStateContext = React.createContext();
  • Context를 내보내줘야 다른 컴포넌트들이 이 컴포넌트에 접근해서 공급하는 데이터를 받아올 수 있으므로 export 해준다.
  • export와 export default와의 차이 : export default는 파일 하나당 하나만 쓸 수 있음. 그러나 export만 쓰면 여러 개로 쓸 수 있음 (ESModule 시스템 참고)
function App() {
  
  return (
    <DiaryStateContext.Provider value={data}>
        <div className="App">
          <DiaryEditor />
          <div>전체 일기 : {data.length}</div>
          <div>기분 좋은 일기 개수 : {goodCount}</div>
          <div>기분 나쁜 일기 개수 : {badCount}</div>
          <div>기분 좋은 일기 비율 : {goodRatio}</div>
          <DiaryList />
        </div>
    </DiaryStateContext.Provider>
  );
};
  • DiaryStateContext 컴포넌트를 만들고 App 컴포넌트가 리턴하고 있는 부분에 최상위 태그로 바꿔준다.
  • 기존 내용을 wrapping 후 React Developer Tools로 확인 (자식 요소가 하위에 묶임)
  • 데이터 공급하기 : value라는 prop으로 data 공급을 내려줘야 함
  • DiaryStateContext가 결론적으로 공급하고 싶은 값은 App 컴포넌트가 가진 data state다. 그래서 value prop에 data state를 보내주기만 하면 우리가 전달하고 싶은 App 컴포넌트의 data state를 잘 받았다는 것을 확인할 수 있다. (모든 데이터를 공급하는 Provider)

☑️ Context로 data 꺼내기

src/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;
  • 기존에 App 컴포넌트로부터 data state를 prop으로 전달받아서 map 함수를 통해 렌더링하고 있었음
  • DiaryList는 결국에는 App 컴포넌트의 data state 값인데, 최상위 Provider가 생겨서 Context로 공급받으면 되니까 기존처럼 App 컴포넌트의 data state 값을 받을 필요가 없어짐
  • Context로부터 받을 수 있도록 변경
  • useContext hook에는 1가지 인자를 전달해야 하는데 data를 꺼내고 싶은 context인 DiaryStateContext를 전달 (import 받은 후)
  • 결론적으로 useContext 기능을 통해서 어떤 context를 지정 후 그 context로부터 이렇게 data를 꺼내올 수 있고, 그 context의 data는 React Developer Tools로 확인할 수 있다.
  • DiaryList에서 context로 data를 꺼내서 쓰게 되므로 App 컴포넌트에서는 더 이상 prop을 전달할 필요가 없어지므로 diaryList={data}를 삭제

☑️ 상태변화 함수에도 Context를 통해서 data 공급하기

src/App.js

export const DiaryDispatchContext = React.createContext();

function App () {

  const memoizedDispatches = useMemo(()=>{
    return {onCreate, onRemove, onEdit}
  }, []);
  
  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도 컴포넌트이기 때문에 prop이 바뀌면 재생성된다. (그 밑에 있는 컴포넌트들도 강제로 재생성된다.)
  • Provider 컴포넌트에게 onCreate, onRemove, onEdit Prop을 똑같이 내려주면 data state가 바뀔 때마다 리렌더링이 일어나서 만들어 둔 최적화가 다 풀리게 되버린다.
  • context를 중첩해서 만들기 (이중문맥) : DiaryStateContext는 모든 데이터를 공급하기 위해서만 최상위에 두고, onCreate, onRemove, onEdit같은 state 변화시키는 함수는 dispatch 함수에 내보내고 있기 때문에 새로운 context를 만들어서 하위에 둔다. (데이터 공급 context - 상태 변화 context)
  • DiaryDispatchContext : 새로운 context 생성 후 DiaryStateContext 자식 요소로 추가
  • memoizedDispatches : onCreate, onRemove, onEdit는 재생성되지 않는 함수들이므로 절대 리렌더링이 되지 않게 묶어준다 (useMemo를 활용해서 재생성되지 않게 객체로 묶어주고 빈 배열로 deps 전달) 그런 다음 DiaryDispatchContext의 value로 전달해준다.
  • DiaryEditor, DiaryList 컴포넌트는 더 이상 onCreate, onRemove, onEdit을 prop으로 전달받을 필요가 없게 됨

☑️ DiaryDispatchContext 전달하기

src/DiaryEditor.js

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

const DiaryEditor = () => {
  const {onCreate} = useContext(DiaryDispatchContext);
};
  • onCreate는 useContext를 통해 DiaryDispatchContext로부터 가져온다
  • DiaryDispatchContext는 onCreate, onEdit, onRemove 함수 3개로 이루어진 객체이기 때문에 비구조화할당을 통해 onCreate를 가져올 수 있다.
  • 정상적으로 일기가 추가되는지 확인

src/DiaryList.js

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>
  );
};
  • 전달했던 prop인 onEdit, onRemove 제거

src/DiaryItem.js

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

const DiaryItem = ({ id, author, content, emotion, created_date
  }) => {
  
    const { onRemove, onEdit } = useContext(DiaryDispatchContext);
    
};
  • 마지막으로 DiaryItem 컴포넌트에서 onEdit과 onRemove를 더 이상 prop으로 받지 않게 변경
  • useContext를 통해 DiaryDispatchContext로부터 onEdit과 onRemove를 받도록 한다
  • 정상적으로 작동하는지 일기 수정, 삭제 해보기


💬 기본 편 드디어 끝!인데 성능 최적화부터 어려운 거 같다.
다음 실전 편(감성일기장) 만들기 전에 복습하기 위해 한번 더 정주행할 예정인데 이해가 안 가는 부분..

💬 난 저 공급자라는 단어를 떠올리면 생각나는 게 있다. "그건 너무 공급자 마인드예요" 그땐 사실 속으론 공급자 솔루션이니까 당연한 거 아닌가? 싶었는데 사용자를 조금 더 넓게 보면 무슨 말인지는 알 것 같다. 공부하다가 문득 생각남..ㅋㅋ

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글