리듀서를 사용하면 컴포넌트의 상태 업데이트 로직을 통합할 수 있습니다. 컨텍스트를 사용하면 다른 컴포넌트에게 깊이 있는 정보를 전달할 수 있습니다. 리듀서와 컨텍스트를 함께 사용하여 복잡한 화면의 상태를 관리할 수 있습니다.
리듀서와 컨텍스트 결합하기
리듀서 소개 예제에서 상태는 리듀서에 의해 관리됩니다. 리듀서 함수는 모든 상태 업데이트 로직을 포함하고 이 파일의 하단에 선언되어 있습니다
https://codesandbox.io/s/u5b2fk?file=/App.js&utm_medium=sandpack
naughty-keldysh-u5b2fk - CodeSandbox
naughty-keldysh-u5b2fk using react, react-dom, react-scripts
codesandbox.io
리듀서는 이벤트 핸들러를 짧고 간결하게 유지하는 데 도움이 됩니다. 그러나 앱이 성장함에 따라 또 다른 어려움에 직면할 수 있습니다. 현재 tasks 상태와 dispatch 함수는 최상위 TaskApp 컴포넌트에서만 사용할 수 있습니다. 다른 컴포넌트가 작업 목록을 읽거나 변경할 수 있도록 하려면 현재 상태와 이를 변경하는 이벤트 핸들러를 명시적으로 프로퍼티로 전달해야 합니다.
예를 들어, TaskApp은 작업 목록과 이벤트 핸들러를 TaskList에 전달합니다
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
그리고 TaskList는 이벤트 핸들러를 Task에 전달합니다
<Task
task={task}
onChange={onChangeTask}
onDelete={onDeleteTask}
/>
이런 작은 예제에서는 잘 작동하지만 중간에 수십 개나 수백 개의 컴포넌트가 있는 경우 모든 상태와 함수를 전달하는 것이 꽤 귀찮을 수 있습니다!
이것이 프로퍼티를 통해 전달하는 대신 tasks 상태와 dispatch 함수를 컨텍스트에 넣고자 하는 이유입니다. 이렇게 하면 TaskApp 트리 아래의 모든 컴포넌트가 작업을 읽고 액션을 디스패치할 수 있으며 반복적인 "프로퍼티 드릴링"이 없습니다.
리듀서와 컨텍스트를 결합하는 방법은 다음과 같습니다
- 컨텍스트를 생성합니다.
- 상태와 디스패치를 컨텍스트에 넣습니다.
- 트리의 어디서든 컨텍스트를 사용합니다.
Step 1: 컨텍스트 생성하기
useReducer 훅은 현재 tasks 목록과 이를 업데이트할 수 있는 dispatch 함수를 반환합니다
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
트리에 전달하려면 두 개의 별도 컨텍스트를 생성합니다
- TasksContext는 현재 작업 목록을 제공합니다.
- TasksDispatchContext는 컴포넌트에서 액션을 디스패치할 수 있는 함수를 제공합니다.
나중에 다른 파일에서 가져올 수 있도록 별도의 파일에서 이들을 내보냅니다
https://codesandbox.io/s/1n71cs?file=/TasksContext.js&utm_medium=sandpack
green-firefly-1n71cs - CodeSandbox
green-firefly-1n71cs using react, react-dom, react-scripts
codesandbox.io
여기서 두 컨텍스트에 기본값으로 null을 전달합니다. 실제 값은 TaskApp 컴포넌트에 의해 제공됩니다.
Step 2: 상태와 디스패치를 컨텍스트에 넣기
이제 TaskApp 컴포넌트에서 두 컨텍스트를 가져올 수 있습니다. useReducer()에서 반환된 tasks와 dispatch를 가져와 아래의 전체 트리에 제공합니다
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와 컨텍스트 모두를 통해 정보를 전달합니다
https://codesandbox.io/s/kfvrn7?file=/App.js&utm_medium=sandpack
keen-antonelli-kfvrn7 - CodeSandbox
keen-antonelli-kfvrn7 using react, react-dom, react-scripts
codesandbox.io
다음 단계에서는 prop 전달을 제거할 것입니다.
Step 3: 트리의 어디에서나 컨텍스트 사용하기
이제 작업 목록이나 이벤트 핸들러를 트리에 전달할 필요가 없습니다
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksDispatchContext.Provider>
</TasksContext.Provider>
대신, 작업 목록이 필요한 모든 컴포넌트가 TaskContext에서 읽을 수 있습니다
export default function TaskList() {
const tasks = useContext(TasksContext);
// ...
작업 목록을 업데이트하기 위해, 컴포넌트는 컨텍스트에서 디스패치 함수를 읽고 호출할 수 있습니다
export default function AddTask() {
const [text, setText] = useState('');
const dispatch = useContext(TasksDispatchContext);
// ...
return (
// ...
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Add</button>
// ...
TaskApp 컴포넌트는 이벤트 핸들러를 전달하지 않고, TaskList는 Task 컴포넌트에 이벤트 핸들러를 전달하지 않습니다. 각 컴포넌트는 필요한 컨텍스트를 읽습니다
https://codesandbox.io/s/8179bl?file=/TaskList.js&utm_medium=sandpack
objective-tristan-8179bl - CodeSandbox
objective-tristan-8179bl using react, react-dom, react-scripts
codesandbox.io
상태는 여전히 최상위 TaskApp 컴포넌트에 있으며, useReducer를 사용하여 관리됩니다. 그러나 tasks와 dispatch는 이제 트리 아래의 모든 컴포넌트에서 이러한 컨텍스트를 가져와 사용함으로써 이용할 수 있습니다.
모든 연결을 단일 파일로 이동하기
이렇게 할 필요는 없지만, 리듀서와 컨텍스트를 단일 파일로 이동하여 컴포넌트를 더 깔끔하게 만들 수 있습니다. 현재 TasksContext.js에는 두 개의 컨텍스트 선언만 포함되어 있습니다
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
이 파일은 바로 지금부터 복잡해질 것입니다! 리듀서를 동일한 파일로 이동합니다. 그런 다음 동일한 파일에 새로운 TasksProvider 컴포넌트를 선언합니다. 이 컴포넌트는 모든 부분을 함께 묶습니다
- 리듀서를 사용하여 상태를 관리합니다.
- 아래의 컴포넌트에 대해 두 컨텍스트를 모두 제공합니다.
- 자식을 prop으로 사용하여 JSX를 전달할 수 있습니다.
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 컴포넌트의 복잡성과 연결이 제거됩니다
https://codesandbox.io/s/7pfy1j?file=/App.js&utm_medium=sandpack
lingering-architecture-7pfy1j - CodeSandbox
lingering-architecture-7pfy1j using react, react-dom, react-scripts
codesandbox.io
TasksContext.js에서 컨텍스트를 사용하는 함수를 내보낼 수도 있습니다
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
컴포넌트가 컨텍스트를 읽어야 할 때 이러한 함수를 통해 수행할 수 있습니다
const tasks = useTasks();
const dispatch = useTasksDispatch();
이렇게 해도 동작이 변경되지 않지만, 나중에 이러한 컨텍스트를 더 분할하거나 이러한 함수에 로직을 추가할 수 있습니다. 이제 컨텍스트와 리듀서 연결은 모두 TasksContext.js에 있습니다. 이렇게 하면 컴포넌트가 깔끔하고 정리되어 있으며, 데이터를 어디서 가져오는지보다 표시하는 내용에 집중할 수 있습니다
https://codesandbox.io/s/nc64qd?file=/TaskList.js&utm_medium=sandpack
hopeful-panna-nc64qd - CodeSandbox
hopeful-panna-nc64qd using react, react-dom, react-scripts
codesandbox.io
트리 아래의 컴포넌트에서 작업을 처리하는 방법에 대한 정보를 가진 화면의 일부로 TasksProvider를 생각할 수 있으며, useTasks는 작업을 읽는 방법이고, useTasksDispatch는 트리 아래의 어떤 컴포넌트에서든 작업을 업데이트하는 방법입니다.
💡 useTasks와 useTasksDispatch와 같은 함수는 *사용자 정의 훅(Custom Hooks)*이라고 합니다. 함수 이름이 use로 시작하면 사용자 정의 훅으로 간주됩니다. 이를 통해 useContext와 같은 다른 훅을 사용할 수 있습니다.
앱이 성장함에 따라 이와 같은 컨텍스트-리듀서 쌍이 많아질 수 있습니다. 이는 앱을 확장하고 트리의 깊은 곳에서 데이터에 액세스하려 할 때 과도한 작업 없이 상태를 올리는 강력한 방법입니다.
요약
- 리듀서와 컨텍스트를 결합하여 어떤 컴포넌트든 그 위의 상태를 읽고 업데이트할 수 있게 합니다.
- 아래 컴포넌트에 상태와 디스패치 함수를 제공하려면:
- 두 개의 컨텍스트를 생성합니다(상태와 디스패치 함수용).
- 리듀서를 사용하는 컴포넌트에서 두 컨텍스트를 모두 제공합니다.
- 필요한 컴포넌트에서 컨텍스트를 사용합니다.
- 모든 연결을 하나의 파일로 이동하여 컴포넌트를 더 정리할 수 있습니다.
- 컨텍스트를 제공하는 TasksProvider와 같은 컴포넌트를 내보낼 수 있습니다.
- 또한 useTasks와 useTasksDispatch와 같은 사용자 정의 훅을 내보내어 읽을 수 있습니다.
- 앱에서 이와 같은 많은 컨텍스트-리듀서 쌍을 가질 수 있습니다.
2023.07.16 - [React/Documentation] - 리액트 공식 문서 번역-23. Refs를 사용해 값 참조하기
리액트 공식 문서 번역-23. Refs를 사용해 값 참조하기
컴포넌트가 어떤 정보를 "기억"하길 원하지만, 그 정보가 새로운 렌더링을 트리거하지 않기를 원할 때 ref를 사용할 수 있습니다. 컴포넌트에 ref 추가하기 React에서 useRef 훅을 가져와 컴포넌트에
ddor2.tistory.com
'React > Documentation' 카테고리의 다른 글
리액트 공식 문서 번역-24. Ref를 사용한 DOM 조작 (0) | 2023.07.17 |
---|---|
리액트 공식 문서 번역-23. Refs를 사용해 값 참조하기 (1) | 2023.07.16 |
리액트 공식 문서 번역-21. Context를 사용해 깊게 데이터 전달하기 (0) | 2023.07.16 |
리액트 공식 문서 번역-20. 상태 로직을 리듀서로 추출하기 (0) | 2023.07.16 |
리액트 공식 문서 번역-19. 상태 보존 및 초기화 (0) | 2023.07.14 |