Reducer와 Context로 확장하기
Reducer를 사용하면 컴포넌트의 상태 업데이트 로직을 통합할 수 있습니다. Context를 사용하면 정보를 깊숙이 다른 컴포넌트에 전달할 수 있습니다. Reducer와 Context를 함께 사용하여 복잡한 화면의 상태를 관리할 수 있습니다.
배울 내용
- Reducer와 Context를 결합하는 방법
- 상태와 디스패치를 props로 전달하지 않는 방법
- Context와 상태 로직을 별도의 파일에 유지하는 방법
Reducer와 Context 결합하기
이 예제는 Reducer 소개에서 가져온 것으로, 상태는 reducer에 의해 관리됩니다. Reducer 함수는 모든 상태 업데이트 로직을 포함하며 이 파일의 하단에 선언되어 있습니다:
리듀서는 이벤트 핸들러를 짧고 간결하게 유지하는 데 도움을 줍니다. 그러나 앱이 커지면 다른 어려움에 부딪힐 수 있습니다.현재 tasks 상태와 dispatch 함수는 최상위 TaskApp컴포넌트에서만 사용할 수 있습니다.다른 컴포넌트가 작업 목록을 읽거나 변경하려면 현재 상태와 이를 변경하는 이벤트 핸들러를 명시적으로props로 전달해야 합니다.
예를 들어,TaskApp은 작업 목록과 이벤트 핸들러를TaskList에 전달합니다:
그리고TaskList는 이벤트 핸들러를Task에 전달합니다:
이처럼 작은 예제에서는 잘 작동하지만, 중간에 수십 개 또는 수백 개의 컴포넌트가 있는 경우 모든 상태와 함수를 아래로 전달하는 것은 꽤나 번거로울 수 있습니다!
이것이 바로 props를 통해 전달하는 대안으로, tasks 상태와 dispatch함수를 모두컨텍스트에 넣고 싶을 수 있는 이유입니다.이렇게 하면 트리에서TaskApp아래에 있는 모든 컴포넌트가 반복적인 "prop drilling" 없이도 tasks를 읽고 액션을 디스패치할 수 있습니다.
리듀서와 컨텍스트를 결합하는 방법은 다음과 같습니다:
- 컨텍스트 생성하기
- 상태와 디스패치를 컨텍스트에 넣기
- 트리 내 어디서든 컨텍스트 사용하기
1단계: 컨텍스트 생성하기
useReducer 훅은 현재 tasks와 이를 업데이트할 수 있는dispatch 함수를 반환합니다:
이들을 트리 아래로 전달하려면 두 개의 별도 컨텍스트를생성해야 합니다:
TasksContext는 현재 작업 목록을 제공합니다.TasksDispatchContext는 컴포넌트가 액션을 디스패치할 수 있는 함수를 제공합니다.
나중에 다른 파일에서 가져올 수 있도록 별도의 파일에서 내보내세요:
여기서는 두 컨텍스트 모두에 기본값으로null을 전달하고 있습니다. 실제 값은 TaskApp컴포넌트에서 제공될 것입니다.
2단계: 상태와 디스패치를 컨텍스트에 넣기
이제 TaskApp컴포넌트에서 두 컨텍스트를 모두 가져올 수 있습니다.useReducer()가 반환한tasks와 dispatch를 가져와서 아래 트리 전체에제공하세요:
현재는 정보를 props와 컨텍스트를 통해 모두 전달하고 있습니다:
다음 단계에서는 prop 전달을 제거할 것입니다.
3단계: 트리 어디에서나 컨텍스트 사용하기
이제 작업 목록이나 이벤트 핸들러를 트리 아래로 전달할 필요가 없습니다:
대신, 작업 목록이 필요한 모든 컴포넌트는TasksContext에서 이를 읽을 수 있습니다:
작업 목록을 업데이트하려면, 모든 컴포넌트는 컨텍스트에서dispatch함수를 읽고 호출할 수 있습니다:
이제TaskApp컴포넌트는 어떤 이벤트 핸들러도 아래로 전달하지 않으며,TaskList 컴포넌트도 Task컴포넌트에 이벤트 핸들러를 전달하지 않습니다.각 컴포넌트는 필요한 컨텍스트를 직접 읽습니다:
상태는 여전히 최상위 TaskApp컴포넌트에 "존재"하며,useReducer로 관리됩니다.하지만 이제tasks와 dispatch는 이러한 컨텍스트를 가져와 사용함으로써 트리 아래의 모든 컴포넌트에서 사용할 수 있습니다.
모든 연결을 하나의 파일로 이동하기
반드시 이렇게 해야 하는 것은 아니지만, 리듀서와 컨텍스트를 모두 하나의 파일로 이동하여 컴포넌트를 더욱 정리할 수 있습니다. 현재TasksContext.js에는 두 개의 컨텍스트 선언만 포함되어 있습니다:
이 파일은 곧 복잡해질 것입니다! 리듀서를 같은 파일로 이동할 것입니다. 그런 다음 같은 파일에 새로운TasksProvider컴포넌트를 선언할 것입니다. 이 컴포넌트는 모든 조각을 하나로 묶을 것입니다:
- 리듀서로 상태를 관리합니다.
- 아래 컴포넌트에 두 컨텍스트를 모두 제공합니다.
- JSX를 전달할 수 있도록children을 prop으로 받습니다.
이렇게 하면 TaskApp컴포넌트에서 모든 복잡성과 연결이 제거됩니다:
또한 사용하는 함수를 TasksContext.js에서 내보낼 수 있습니다:
컴포넌트가 컨텍스트를 읽어야 할 때는 이러한 함수를 통해 수행할 수 있습니다:
이렇게 해도 동작은 전혀 변하지 않지만, 나중에 이러한 컨텍스트를 더 세분화하거나 이 함수들에 논리를 추가할 수 있게 해줍니다.이제 모든 컨텍스트와 리듀서 연결은TasksContext.js에 있습니다. 이렇게 하면 컴포넌트가 깔끔하고 복잡하지 않게 유지되며, 데이터를 어디서 가져오는지보다 무엇을 표시하는지에 집중할 수 있습니다:
여러분은 TasksProvider를 작업을 처리하는 방법을 아는 화면의 일부로,useTasks를 작업을 읽는 방법으로, 그리고useTasksDispatch를 트리 아래의 어떤 컴포넌트에서도 작업을 업데이트하는 방법으로 생각할 수 있습니다.
참고
함수 useTasks와 useTasksDispatch는 커스텀 훅이라고 합니다. 함수 이름이 use로 시작하면 커스텀 훅으로 간주됩니다. 이를 통해useContext와 같은 다른 훅을 그 안에서 사용할 수 있습니다.
앱이 커지면 이와 같은 컨텍스트-리듀서 쌍이 많아질 수 있습니다. 이는 앱을 확장하고 트리 깊숙이 있는 데이터에 접근하고 싶을 때마다 많은 작업 없이상태를 끌어올리는 강력한 방법입니다.
요약
- 리듀서와 컨텍스트를 결합하여 상위의 상태를 읽고 업데이트할 수 있는 컴포넌트를 만들 수 있습니다.
- 하위 컴포넌트에 상태와 디스패치 함수를 제공하려면:
- 두 개의 컨텍스트를 생성합니다(상태용과 디스패치 함수용).
- 리듀서를 사용하는 컴포넌트에서 두 컨텍스트를 모두 제공합니다.
- 해당 컨텍스트를 읽어야 하는 컴포넌트에서 적절한 컨텍스트를 사용합니다.
- 모든 연결 코드를 하나의 파일로 옮겨 컴포넌트를 더욱 정리할 수 있습니다.
- 컨텍스트를 제공하는
TasksProvider와 같은 컴포넌트를 내보낼 수 있습니다. - 또한
useTasks및useTasksDispatch와 같은 커스텀 훅을 내보내어 읽을 수 있게 할 수 있습니다.
- 컨텍스트를 제공하는
- 앱 내에 이러한 컨텍스트-리듀서 쌍을 여러 개 가질 수 있습니다.
