상세 컨텐츠

본문 제목

[리액터 스타터2] 9장. 컴포넌트 트리에 데이터 공급하기

23-24/React.js 2

by YUZ 유즈 2024. 1. 5. 10:00

본문

728x90


1. Context

리액트 컴포넌트 트리 전체를 대상으로 같은 문맥 아래에 있는 컴포넌트 그룹에 데이터를 공급하는 기능이다.

 

<Context를 사용하는 이유>

리액트 컴포넌트 계층 구조에서 컴포넌트 간에 값을 전달할 때 발생하는 Props Drilling 문제를 해결하기 위해 사용한다.

Context를 이용하면 단계마다 일일이 Props를 전달하지 않고도 컴포넌트 트리 전역에 데이터를 공급할 수 있어 Props Drilling 문제를 간단하게 해결할 수 있다.


2. ContextAPI

Context를 만들고 다루는 리액트 기능으로 createContext, Context.Provider 등이 있다.

 

<Context 만들기>

 

React.createContext를 이용하면 새로운 Context를 만들 수 있다.

import React from "react";
const MyContext = React.createContext(defaultValue);
  • 인수로 전달하는 값은 Context의 기본값으로 생략할 수 있다.

 

<Context에 데이터 공급하고 사용하기>

import React from "react";
const MyContext = React.createContext(defaultValue);

function App() {
	const data = "data";
	return (
		<div>
			<Header />
			<MyContext.Provider value={data}>
				<Body />
			</MyContext.Provider>
		<div>
	);
}

function Main() {
	const data = useContext(MyContext);
	( ... )
}
  • Context.Provider라는 Context객체에 기본으로 포함된 컴포넌트를 사용한다.
    → Provider 컴포넌트에 Props(value)를 전달해 MyContext가 공급할 값을 설정한다.

  • useContext : 특정 Context가 공급하는 데이터를 불러오는 리액트 훅이다.
    → 자신이 속한 그룹의 Context가 제공하는 값을 불러올 수 있다. 호출한 컴포넌트가 인수로 전달한 Context 그룹에 속해 있지 않으면 오류가 발생한다.

-> 정리하면 createContext를 이용해 Context를 만들고, 값을 공급할 컴포넌트를 Context.Provider로 감싼 다음 함수 useContext를 호출해 Context가 공급하는 값을 불러와 사용한다.

 


3. Context로 [할 일 관리] 앱 리팩토링 하기 

<리팩토링>

사용자에게 제공하는 기능은 변경하지 않으면서 내부 구조를 개선하는 작업이다.

 

<어떻게 Context 적용할지 생각해 보기>

TodoItem 컴포넌트가 Props를 사용하려면 TodoList 컴포넌트를 거쳐서 전달해야 해 Props Drilling 문제가 발생한다.

→ TodoContext라는 이름의 Context를 만들고, App 컴포넌트 하위의 데이터를 공급하는 TodoContext.Provider를 배치한다. 그리고 해당 Provider 아래에 TodoEditor, TodoList, TodoItem 컴포넌트를 배치한다.

import React, { useCallback, useReducer, useRef } from "react";
( ... )
const TodoContext = React.createContext();

return (
    <div className="App">
      <Header />
      <TodoContext.Provider value={{ todo, onCreate, onUpdate, onDelete }}>
        <TodoEditor />
        <TodoList />
      </TodoContext.Provider>
    </div>
  );
}
export default App;
  • Context는 반드시 컴포넌트 밖에서 생성해야 한다는 점을 유의해야 한다.
  • Provider 배치하고 데이터를 공급하기 위해 Props(value)를 설정한다.
    → 하위에 배치한 컴포넌트는 다른 경로로 Props를 받을 필요 없으니 해당 부분을 삭제한다.
  • todo 값이 undefined로 length 프로퍼티로 접근하면 오류가 발생하기에 TodoList 컴포넌트에 todo의 기본값을 빈 배열로 하는 defaultProps를 설정한다.
const TodoList = ({ todo, onUpdate, onDelete }) => {
( ... )
}

TodoList.defaultProps = {
	todo: [],
};
export default TodoList;

 


4. 각 컴포넌트에서 Context 데이터 사용하기 

App.js에 선언한 TodoContext를 다른 파일에서 불러오기 위해선 export로 내보내야 한다.

( ... )
export const TodoContext = React.createContext();
( ... )

 

<TodoList 컴포넌트>

import { useContext, useMemo, useState } from "react";
import { TodoContext } from "../App";
( ... )

const TodoList = () => {
	const { todo } = useContext(TodoContext);
	( ... )
	return (
	<div className="TodoList">
		( ... )
		<div className="list_wrapper">
        	{getSearchResult().map((it) => ( 
          		<TodoItem
       	  	 	key={it.id}
        	 	{...it}
      		/>
      		))}
      	</div>
    </div>
  );
};
export default TodoList;
  • TodoList 컴포넌트는 더 이상 App에서 어떤 Props도 받지 않는다. 따라서 Props를 매개 변수로 구조 분해 할당하는 기존 코드를 제거한다.
  • TodoItem에 함수를 전달할 필요성이 사라졌으므로 해당 부분을 제거한다.

->  나머지 다른 컴포넌트도 위와 같이 매개변수로 구조 분해 할당하던 기존 코드를 변경하면 된다.

 


5. 리팩토링 잘 되었는지 확인하기 

<문제의 원인 파악하기>

todo가 변하면 TodoContext.Provider에서 전달하는 모든 Props도 변하는 문제가 발생한다.

원인은 State 변수 todo와 dispatch관련 함수들이 하나의 객체로 묶여 동일한 Context에 Props로 전달되기 때문이다.

 

 

<구조 재설계하기>

Context를 역할에 따라 분리하기

  • TodoStateContext : todo 업데이트되면 영향받는 컴포넌트 위한 Context이다.
  • TodoDispatchContext : dispatch 함수 onCreate, onUpdate, onDelete가 변경되면 영향받는 컴포넌트 위한 Context이다.
export const TodoStateContext = React.createContext();
export const TodoDispatchContext = React.createContext();
( ... )

function App() {
  ( ... )
	const memoizedDispatches = useMemo(() => {
	    return { onCreate, onUpdate, onDelete };
	 }, []);
	
  return (
    <div className="App">
      <Header />
      <TodoStateContext.Provider value={ todo }>
        <TodoDispatchContext.Provider value={memoizedDispatches}>
          <TodoEditor />
          <TodoList />
        </TodoDispatchContext.Provider>
      </TodoStateContext.Provider>
    </div>
  );
}
export default App;
  • App 컴포넌트에서 todo 공급할 TodoStateContext와 dispatch 함수를 공급할 TodoDispatchContext를 만들어 배치한다.
  • useMeomo를 이용해  TodoDispatchContext에 전달한 dispatch 함수를 다시 생성하지 않도록 한다.

 

<Context에서 데이터를 받는 자식 컴포넌트 수정하기>

  • TodoEditor 컴포넌트
import { TodoDispatchContext } from "../App";
( ... )

const TodoEditor = () => {
  const { onCreate } = useContext(TodoDispatchContext);
	( ... )
};

 

-> 나머지 컴포넌트도 알맞은 Context에서 데이터를 받도록 수정한다.

 

 

<리팩토링 잘 되었는지 확인하기 2>

최적화를 위해 TodoContext를 TodoStateContext와 TodoDispatchContext로 분리하였다.

이제 새로운 할 일 아이템을 생성하면 생성한 TodoItem만 렌더링하고 나머지 컴포넌트는 더 이상 리렌더되지 않는다.

리랙토링 최종 확인


 


Quiz

1. (        )란 은 문맥 아래에 있는 컴포넌트 그룹에 데이터를 공급하는 기능이다.
2. ( 1 )은 리액트 컴포넌트 계층 구조에서 컴포넌트 간에 값을 전달할 때 발생하는 (        ) 문제를 해결하기 위해 사용된다. 
3. ( 1 )를 만들고 다루는 리액트 기능으로 (        ,        ) 등이 있다.
4. (        )는 특정 Context가 공급하는 데이터를 불러오는 리액트 훅이다.
5. ( 1 )은 반드시 컴포넌트 (        )에서 생성해야 한다는 점을 유의해야 한다.
6. (        )는 todo 업데이트되면 영향받는 컴포넌트 위한 Context이다.
7. (         )는 dispatch 함수 onCreate, onUpdate, onDelete가 변경되면 영향받는 컴포넌트 위한 Context이다.

 


 

 

1. TodoList 컴포넌트는 할 일 데이터인 todo를 사용하므로, 이것을 TodoStateContext에서 받도록 수정해라.


2. TodoItem 컴포넌트는 할 일을 수정하고 삭제하는 함수 onUpdate와 onDelete를 사용하므로 TodoDispatchContext에서 해당 함수를 받도록 수정해라.

 

 

 

 


 

 

Context / Props Drilling / createContext, Context.Provider / useContext / 밖 / TodoStateContext / TodoDispatchContext


1.

( ... )
import { TodoStateContext } from "../App";
( ... )
const TodoList = () => {
  const todo = useContext(TodoStateContext);
	( ... )
};
export default TodoList;

 

2.

( ... )
import { TodoDispatchContext } from "../App";
( ... )
const TodoItem = ({ id, content, isDone, createdDAte }) => {
  console.log(`${id} TodoItem 업데이트`);
  const { onUpdate, onDelete } = useConetxt(TodoDispatchContext);
	( ... )
};
export default React.memo(TodoItem);

 


 

출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023),  p396-407.

Editor:  yunseul

728x90

관련글 더보기