리액트 컴포넌트 트리 전체를 대상으로 같은 문맥 아래에 있는 컴포넌트 그룹에 데이터를 공급하는 기능이다.
<Context를 사용하는 이유>
리액트 컴포넌트 계층 구조에서 컴포넌트 간에 값을 전달할 때 발생하는 Props Drilling 문제를 해결하기 위해 사용한다.
Context를 이용하면 단계마다 일일이 Props를 전달하지 않고도 컴포넌트 트리 전역에 데이터를 공급할 수 있어 Props Drilling 문제를 간단하게 해결할 수 있다.
Context를 만들고 다루는 리액트 기능으로 createContext, Context.Provider 등이 있다.
<Context 만들기>
React.createContext를 이용하면 새로운 Context를 만들 수 있다.
import React from "react";
const MyContext = React.createContext(defaultValue);
<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);
( ... )
}
-> 정리하면 createContext를 이용해 Context를 만들고, 값을 공급할 컴포넌트를 Context.Provider로 감싼 다음 함수 useContext를 호출해 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;
const TodoList = ({ todo, onUpdate, onDelete }) => {
( ... )
}
TodoList.defaultProps = {
todo: [],
};
export default TodoList;
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;
-> 나머지 다른 컴포넌트도 위와 같이 매개변수로 구조 분해 할당하던 기존 코드를 변경하면 된다.
<문제의 원인 파악하기>
todo가 변하면 TodoContext.Provider에서 전달하는 모든 Props도 변하는 문제가 발생한다.
원인은 State 변수 todo와 dispatch관련 함수들이 하나의 객체로 묶여 동일한 Context에 Props로 전달되기 때문이다.
<구조 재설계하기>
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;
<Context에서 데이터를 받는 자식 컴포넌트 수정하기>
import { TodoDispatchContext } from "../App";
( ... )
const TodoEditor = () => {
const { onCreate } = useContext(TodoDispatchContext);
( ... )
};
-> 나머지 컴포넌트도 알맞은 Context에서 데이터를 받도록 수정한다.
<리팩토링 잘 되었는지 확인하기 2>
최적화를 위해 TodoContext를 TodoStateContext와 TodoDispatchContext로 분리하였다.
이제 새로운 할 일 아이템을 생성하면 생성한 TodoItem만 렌더링하고 나머지 컴포넌트는 더 이상 리렌더되지 않는다.
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
[리액터 스타터2] 리덕스(Redux) (0) | 2024.01.19 |
---|---|
[리액터 스타터2] project 3 [감정 일기장] 만들기 (0) | 2024.01.12 |
[리액터 스타터2] 7장. useReducer와 상태 관리 / 8장. 최적화 (2) | 2023.12.29 |
[리액터 스타터2] project 2 [할 일 관리] 앱 만들기 2 (0) | 2023.12.23 |
[리액터 스타터2] project 2 [할 일 관리] 앱 만들기 1 (0) | 2023.12.01 |