https://react.dev/learn/scaling-up-with-reducer-and-context를 정리한다.
Reducer와 Context를 같이 사용하여 복잡한 화면의 상태를 관리할 수 있다.
- Reducer를 사용하면 컴포넌트의 state 업데이트 logic을 통합할 수 있다.
- context를 사용하면 정보를 다른 컴포넌트로 deep하게 전달할 수 있다.
앱이 성장함에 따라 이와 같은 context-reducer 쌍이 많이 있을 수 있다. 이는 트리의 깊숙한 데이터에 액세스하려고 할 때마다 너무 많은 작업 없이 앱을 확장하고 state를 위로 올릴 수 있는 강력한 방법이다.
Reducer와 Context 같이 사용하기
Reducer 소개 단원 예제에서 state는 reducer로 관리되고 있다. reducer 함수는 모든 state update logic을 포함한다.
reducer는 이벤트 핸들러를 짧고 간결하게 유지하는 데 도움이 된다. 그러나 앱이 성장함에 따라 다른 어려움이 생길 수 있다. "현재 tasks state와 dispatch function은 최상위 컴포넌트(TaskApp)에서만 사용할 수 있다".
다른 컴포넌트가 task 목록을 읽거나 변경할 수 있도록 하려면 현재 상태와 이를 변경하는 이벤트 핸들러를 props을 사용해 명시적으로 전달해야한다.
// TaskApp은 task 목록과 이벤트 핸들러를 TaskList로 전달한다.
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
// TaskList는 Task에 이벤트 핸들러를 전달한다.
<Task
task={task}
onChange={onChangeTask}
onDelete={onDeleteTask}
/>
위의 예제는 잘 돌아가지만 중간에 많은 수의 컴포넌트가 있을 경우, 모든 state와 function을 아래로 전달해야하고 이는 별로다!
이를 개선하기 위해 tasks state와 dispatch함수를 모두 컨텍스트에 넣을 수 있다. 이 방법은 트리에서 TaskApp아래의 모든 컴포넌트가 반복적인 "prop drilling" 없이 tasks을 읽고 action을 dispatch할 수 있습니다.
다음은 리듀서를 컨텍스트와 결합하는 방법입니다.
1. context를 만든다.
`useReducer` Hook은 현재 `tasks`와 이를 업데이트 할 수 있는 `dispatch` 함수를 반환한다.
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
트리 아래로 반환된 값을 전달하기 위해, 두개의 분리된 context를 생성 후 다른 파일에서 import할 수 있게 export한다.
- `TasksContext` : 현재 task 목록을 제공
- `TastsDispatchContext` : 컴포넌트가 dispatch action을 할 수 있는 함수를 제공
// TasksContext.js
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
현재 두 context는 default value로 `null`을 전달하고 있다. 실제 값은 `TaskApp` 컴포넌트에게 제공받을 것이다.
2. statue와 dispatch를 context에 넣는다.
`TaskApp` 컴포넌트에서 두 context를 import한 후, `useReducer()`에서 반환된 `tasks`와 `dispatch`를 context에 제공한다.
import { TasksContext, TasksDispatchContext } from './TasksContext.js'; // *
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
// ...
return (
<TasksContext.Provider value={tasks}> // *
<TasksDispatchContext.Provider value={dispatch}> // *
...
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
지금은 두 곳(props, context)에서 정보를 전달하고 있다. 다음 차례에 props를 제거하자.
3. 트리의 아무 곳에서나 context를 사용한다.
이제 task 목록과 이벤트 핸들러를 아래로 내리지 않아도 된다.
대신 `TaskContext`를 통해 어떤 컴포넌트든 task 목록이 필요하면 읽을 수 있다.
const tasks = useContext(TasksContext);
또한, `TaskDispatchContext`를 통해 task 목록을 업데이트 하기위해 어떤 컴포넌트든 `dispatch` 함수를 읽을 수 있다.
const dispatch = useContext(TasksDispatchContext);
위의 모든 단계를 적용하면 아래와 같다.
state는 여전히 top-level(TaskApp) 컴포넌트에 존재하고, `useReducer`로 관리된다. 그러나 `tasks`, `dispatch`는 context를 사용해 아래의 모든 컴포넌트에서 사용할 수 있다.
모든 것을 하나의 파일로 옮기기
이렇게 할 필요는 없지만 reducer와 context를 단일 파일로 이동하여 컴포넌트를 더 깔끔하게 정리할 수 있습니다.
현재 TasksContext.js는 두 개의 컨텍스트 선언만 포함하고 있다.
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
reducer를 동일한 파일로 이동한 후, 새로운 `TasksProvider` 컴포넌트를 선언한다. 이 컴포넌트는 모든 조각을 함께 묶는다.
- reducer로 상태를 관리한다.
- 아래 구성 요소에 두 context를 모두 제공한다.
- JSX 를 전달할 수 있도록 prop으로 children를 사용한다.
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
이러면 `TaskApp` 컴포넌트에 있는 복잡성과 wiring을 제거할 수 있다.(아래에서 App.js, TasksContext.js를 확인해보라)
추가로, TasksContext.js에서 context를 사용하는 함수를 export할 수 있다.
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
컴포넌트가 context를 읽어야할 때, 이 함수를 사용할 수 있다.
const tasks = useTasks();
const dispatch = useTasksDispatch();
동작이 절대 변경되지 않지만, 나중에 이런 context를 더 분할하거나 일부 logic를 추가할 수 있다. 이제 모든 context 및 reducer wiring이 TasksContext.js에 있다. 이렇게 하면 데이터를 가져오는 위치보다 표시되는 내용에 집중하여 컴포넌트를 깨끗하고 깔끔하게 유지한다.(TaskList 참고)
결과적으로 아래처럼 분리해서 생각할 수 있다.
- `TasksProvider` : tasks를 다루는 방법을 아는 part of the screen
- `useTasks` : tasks를 읽는 방법
- `useTasksDispatch` : tasks를 변경하는 방법
`userTask`, `userTasksDispatch` 함수는 커스텀 훅이라고 불린다. 이름이 use로 시작하는 함수는 커스텀 훅이다. 이렇게 하면 내부에서 다른 `useContext`와 같은 다른 훅을 사용할 수 있다.
'React > Learn React' 카테고리의 다른 글
Adding Interactivity ☞ Queueing a Series of State Updates (0) | 2022.12.04 |
---|---|
Escape Hatches ☞ Referencing Values with Refs (0) | 2022.12.04 |
Managing State ☞ Passing Data Deeply with Context (0) | 2022.10.30 |
Managing State ☞ Extracting State Logic into a Reducer (1) | 2022.10.03 |
Managing State ☞ Sharing State Between Components (0) | 2022.10.01 |