본문 바로가기
React/Documentation

리액트 공식 문서 번역-22. 리듀서와 컨텍스트를 확장하기

by 똘이토스 2023. 7. 16.

리듀서를 사용하면 컴포넌트의 상태 업데이트 로직을 통합할 수 있습니다. 컨텍스트를 사용하면 다른 컴포넌트에게 깊이 있는 정보를 전달할 수 있습니다. 리듀서와 컨텍스트를 함께 사용하여 복잡한 화면의 상태를 관리할 수 있습니다.

 

리듀서와 컨텍스트 결합하기

리듀서 소개 예제에서 상태는 리듀서에 의해 관리됩니다. 리듀서 함수는 모든 상태 업데이트 로직을 포함하고 이 파일의 하단에 선언되어 있습니다

 

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 트리 아래의 모든 컴포넌트가 작업을 읽고 액션을 디스패치할 수 있으며 반복적인 "프로퍼티 드릴링"이 없습니다.

 

리듀서와 컨텍스트를 결합하는 방법은 다음과 같습니다

 

  1. 컨텍스트를 생성합니다.
  2. 상태와 디스패치를 컨텍스트에 넣습니다.
  3. 트리의 어디서든 컨텍스트를 사용합니다.

 

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()에서 반환된 tasksdispatch를 가져와 아래의 전체 트리에 제공합니다

 

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 컴포넌트는 이벤트 핸들러를 전달하지 않고, TaskListTask 컴포넌트에 이벤트 핸들러를 전달하지 않습니다. 각 컴포넌트는 필요한 컨텍스트를 읽습니다

 

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를 사용하여 관리됩니다. 그러나 tasksdispatch는 이제 트리 아래의 모든 컴포넌트에서 이러한 컨텍스트를 가져와 사용함으로써 이용할 수 있습니다.

 

모든 연결을 단일 파일로 이동하기

이렇게 할 필요는 없지만, 리듀서와 컨텍스트를 단일 파일로 이동하여 컴포넌트를 더 깔끔하게 만들 수 있습니다. 현재 TasksContext.js에는 두 개의 컨텍스트 선언만 포함되어 있습니다

 

import { createContext } from 'react';

export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);

 

이 파일은 바로 지금부터 복잡해질 것입니다! 리듀서를 동일한 파일로 이동합니다. 그런 다음 동일한 파일에 새로운 TasksProvider 컴포넌트를 선언합니다. 이 컴포넌트는 모든 부분을 함께 묶습니다

 

  1. 리듀서를 사용하여 상태를 관리합니다.
  2. 아래의 컴포넌트에 대해 두 컨텍스트를 모두 제공합니다.
  3. 자식을 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는 트리 아래의 어떤 컴포넌트에서든 작업을 업데이트하는 방법입니다.

💡 useTasksuseTasksDispatch와 같은 함수는 *사용자 정의 훅(Custom Hooks)*이라고 합니다. 함수 이름이 use로 시작하면 사용자 정의 훅으로 간주됩니다. 이를 통해 useContext와 같은 다른 훅을 사용할 수 있습니다.

 

앱이 성장함에 따라 이와 같은 컨텍스트-리듀서 쌍이 많아질 수 있습니다. 이는 앱을 확장하고 트리의 깊은 곳에서 데이터에 액세스하려 할 때 과도한 작업 없이 상태를 올리는 강력한 방법입니다.

 

요약

  • 리듀서와 컨텍스트를 결합하여 어떤 컴포넌트든 그 위의 상태를 읽고 업데이트할 수 있게 합니다.
  • 아래 컴포넌트에 상태와 디스패치 함수를 제공하려면:
    1. 두 개의 컨텍스트를 생성합니다(상태와 디스패치 함수용).
    2. 리듀서를 사용하는 컴포넌트에서 두 컨텍스트를 모두 제공합니다.
    3. 필요한 컴포넌트에서 컨텍스트를 사용합니다.
  • 모든 연결을 하나의 파일로 이동하여 컴포넌트를 더 정리할 수 있습니다.
    • 컨텍스트를 제공하는 TasksProvider와 같은 컴포넌트를 내보낼 수 있습니다.
    • 또한 useTasksuseTasksDispatch와 같은 사용자 정의 훅을 내보내어 읽을 수 있습니다.
  • 앱에서 이와 같은 많은 컨텍스트-리듀서 쌍을 가질 수 있습니다.

 

2023.07.16 - [React/Documentation] - 리액트 공식 문서 번역-23. Refs를 사용해 값 참조하기

 

리액트 공식 문서 번역-23. Refs를 사용해 값 참조하기

컴포넌트가 어떤 정보를 "기억"하길 원하지만, 그 정보가 새로운 렌더링을 트리거하지 않기를 원할 때 ref를 사용할 수 있습니다. 컴포넌트에 ref 추가하기 React에서 useRef 훅을 가져와 컴포넌트에

ddor2.tistory.com