[React/리액트] ContextAPI 활용 - useReducer로 dispatch context 만들기
(ContextAPI 이전글 바로가기)
TodoList를 만들면서 이런 식으로 TodoList에서는 사용하지 않는 기능을 TodoItem에 넘겨주기 위해서 중간다리 역할로 넘겨주고 있으므로 이를 ContextAPI를 이용해서 관리하고자 한다.
이런 작은 프로젝트에서는 ContextAPI를 꼭 사용하지 않아도 되지만, 대규모 프로젝트나 실무에서는 context API를 사용하면 편리하다.
ContextAPI를 통해 컴포넌트에서 dispatch로 참조하는 방법은 다음과 같다.
useReducer 를 사용하여 상태를 관리하는 컴포넌트를 만든다. >> TodoProvider
state
와 dispatch
를 Context 를 통하여 다른 컴포넌트에서 바로 사용할 수 있게 해줄 것이다.
우리는 하나의 Context 를 만들어서 state
와 dispatch
를 함께 넣어주는 대신에, 2개의 Context를 만들어서 따로따로 넣어줄 것이다. 이렇게 하면 dispatch
만 필요한 컴포넌트에서 불필요한 렌더링을 방지할 수 있다.
추가적으로, 사용하게 되는 과정에서 더욱 편리하기도 하다.
Context 에서 사용할 값을 지정할 때에는 Provider 컴포넌트를 렌더링하고 value
를 설정해주면 된다.
그리고 props 로 받아온 children
값을 내부에 렌더링 해준다.
이렇게 하면 다른 컴포넌트에서 state
나 dispatch
를 사용하고 싶을 때 다음과 같이 할 수 있다.
사용법
import React, { createContext, useReducer, useContext, useRef } from "react";
/* 1. 초기상태(initialState)을 설정해준다. */
const initialTodos = [
{
id: 1,
text: "프로젝트 생성하기",
done: true,
},
{
id: 2,
text: "컴포넌트 스타일링하기",
done: true,
},
{
id: 3,
text: "Context 만들기",
done: false,
},
{
id: 4,
text: "기능 구현하기",
done: false,
},
];
/*
2. reducer 함수를 생성한다.
CREATE
TOGGLE
REMOVE
*/
function todoReducer(state, action) {
switch (action.type) {
case "CREATE":
return state.concat(action.todo);
case "TOGGLE":
return state.map((todo) =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
case "REMOVE":
return state.filter((todo) => todo.id !== action.id);
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
/* 4. Context를 통해 전달해줄 값을 생성한다. */
const TodoStateContext = createContext();
const TodoDispatchContext = createContext();
const TodoNextIdContext = createContext();
export function TodoProvider({ children }) {
/* 3. useReducer를 사용하여 reducer 함수와 초기상태를 받아온다. */
const [state, dispatch] = useReducer(todoReducer, initialTodos);
const nextId = useRef(5);
/* 5. 사용할 때 Provider 컴포넌트(공통 부모 컴포넌트에 사용)를 이용하여 value에 전달해줄 값을 지정해준다. */
return (
<TodoStateContext.Provider value={state}>
<TodoDispatchContext.Provider value={dispatch}>
<TodoNextIdContext.Provider value={nextId}>
{children}
</TodoNextIdContext.Provider>
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
);
}
/* 6. 커스텀Hooks //
useContext를 이용하여 createContext 로 생성한 전역 데이터 값을 조회하여 사용할 수 있다.
이렇게 Provider에 의하여 감싸진 컴포넌트는 어디서든지 Context 값을 다른 곳에서 조회해서 사용할 수 있다.*/
export function useTodoState() {
return useContext(TodoStateContext);
}
export function useTodoDispatch() {
return useContext(TodoDispatchContext);
}
export function useTodoNextId() {
return useContext(TodoNextIdContext);
}
Context가 없는 경우에 에러띄우기
export function useTodoState() {
const context = useContext(TodoStateContext);
if (!context) {
throw new Error('Cannot find TodoProvider');
}
return context;
}
컴포넌트 TodoProvider 로 감싸기
이제 위에서 만든 Todo 관련 Context 를 프로젝트 모든 곳에서 사용할 수 있도록, App 컴포넌트에서 TodoProvider 를 불러와서 모든 내용을 TodoProvider 로 감싸준다
App.js
import React from 'react';
import { createGlobalStyle } from 'styled-components';
import TodoTemplate from './components/TodoTemplate';
import TodoHead from './components/TodoHead';
import TodoList from './components/TodoList';
import TodoCreate from './components/TodoCreate';
import { TodoProvider } from './TodoContext';
const GlobalStyle = createGlobalStyle`
body {
background: #e9ecef;
}
`;
function App() {
return (
<TodoProvider>
<GlobalStyle />
<TodoTemplate>
<TodoHead />
<TodoList />
<TodoCreate />
</TodoTemplate>
</TodoProvider>
);
}
export default App;