-
[React] Todolist 만들기 정리(2)📌 React 2022. 9. 28. 23:41
Todolist 만들기 정리(1) 에 이어서 작업을 할 예정이다.
이전 게시글에는 UI 작업이었다면 이번 편은 기능적인 것을 보여줄 것이다.
리듀서 만들기
useReducer 를 사용하여 상태를 관리하는 TodoProvider 라는 컴포넌트 생성한다.
src/TodoContext.js
import React, { useReducer } from 'react'; const initialTodos = [ { id: 1, text: '프로젝트 생성하기', done: true }, { id: 2, text: '컴포넌트 스타일링하기', done: true }, { id: 3, text: 'Context 만들기', done: false }, { id: 4, text: '기능 구현하기', done: false } ]; 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}`); } } export function TodoProvider({ children }) { const [state, dispatch] = useReducer(todoReducer, initialTodos); return children; }
Context 만들기
state 와 dispatch 를 Context 통하여 다른 컴포넌트에서 바로 사용 할 수 있게 구현한다.
하나의 Context 를 만들어서 state 와 dispatch 를 함께 넣어주는 대신에, 두개의 Context 를 만들어서 따로 넣어준다.
✨ 이유?
- 이렇게 하면 dispatch 만 필요한 컴포넌트에서 불필요한 렌더링을 방지 할 수 있다.
- 사용하게 되는 과정에서 더욱 편리하다.
src/TodoContext.js
import React, { useReducer, createContext } from 'react'; const initialTodos = [ { id: 1, text: '프로젝트 생성하기', done: true }, { id: 2, text: '컴포넌트 스타일링하기', done: true }, { id: 3, text: 'Context 만들기', done: false }, { id: 4, text: '기능 구현하기', done: false } ]; 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}`); } } const TodoStateContext = createContext(); const TodoDispatchContext = createContext(); export function TodoProvider({ children }) { const [state, dispatch] = useReducer(todoReducer, initialTodos); return ( <TodoStateContext.Provider value={state}> <TodoDispatchContext.Provider value={dispatch}> {children} </TodoDispatchContext.Provider> </TodoStateContext.Provider> ); }
다른 컴포넌트에서 state 나 dispatch를 사용하고 싶을 때 아래와 같이 사용할 수 있다.
import React, { useContext } from 'react'; import { TodoStateContext, TodoDispatchContext } from '../TodoContext'; function Sample() { const state = useContext(TodoStateContext); const dispatch = useContext(TodoDispatchContext); return <div>Sample</div>; }
커스텀 Hook 만들기
컴포넌트에서 useContext 를 직접 사용하는 대신에,
useContext 를 사용하는 커스텀 Hook 을 만들어 내보내기해서 사용한다.
import React, { useReducer, createContext, useContext } from 'react'; const initialTodos = [ { id: 1, text: '프로젝트 생성하기', done: true }, { id: 2, text: '컴포넌트 스타일링하기', done: true }, { id: 3, text: 'Context 만들기', done: false }, { id: 4, text: '기능 구현하기', done: false } ]; 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}`); } } const TodoStateContext = createContext(); const TodoDispatchContext = createContext(); export function TodoProvider({ children }) { const [state, dispatch] = useReducer(todoReducer, initialTodos); return ( <TodoStateContext.Provider value={state}> <TodoDispatchContext.Provider value={dispatch}> {children} </TodoDispatchContext.Provider> </TodoStateContext.Provider> ); } export function useTodoState() { return useContext(TodoStateContext); } export function useTodoDispatch() { return useContext(TodoDispatchContext); }
이것을 사용하고 싶을 때 아래와 같이 사용할 수 있다.
import React from 'react'; import { useTodoState, useTodoDispatch } from '../TodoContext'; function Sample() { const state = useTodoState(); const dispatch = useTodoDispatch(); return <div>Sample</div>; }
nextId 값 관리하기
nextId 값을 위한 Context 를 만들 때에도 마찬가지로 useTodoNextId 라는 커스텀 Hook을 따로 만들어준다.
nextId 가 의미하는 값은 새로운 항목을 추가 할 때 사용할 고유 ID 이다.
이 값은, useRef 를 사용하여 관리해주도록 한다.
import React, { useReducer, createContext, useContext, useRef } from 'react'; const initialTodos = [ { id: 1, text: '프로젝트 생성하기', done: true }, { id: 2, text: '컴포넌트 스타일링하기', done: true }, { id: 3, text: 'Context 만들기', done: false }, { id: 4, text: '기능 구현하기', done: false } ]; 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}`); } } const TodoStateContext = createContext(); const TodoDispatchContext = createContext(); const TodoNextIdContext = createContext(); export function TodoProvider({ children }) { const [state, dispatch] = useReducer(todoReducer, initialTodos); const nextId = useRef(5); return ( <TodoStateContext.Provider value={state}> <TodoDispatchContext.Provider value={dispatch}> <TodoNextIdContext.Provider value={nextId}> {children} </TodoNextIdContext.Provider> </TodoDispatchContext.Provider> </TodoStateContext.Provider> ); } export function useTodoState() { return useContext(TodoStateContext); } export function useTodoDispatch() { return useContext(TodoDispatchContext); } export function useTodoNextId() { return useContext(TodoNextIdContext); }
커스텀 Hook 에서 에러 처리
useTodoState, useTodoDispatch, useTodoNextId Hook 을 사용하려면,
해당 컴포넌트가 TodoProvider 컴포넌트 내부에 렌더링되어 있어야 한다.
(예: App 컴포넌트에서 모든 내용을 TodoProvider 로 감싸기).
만약 TodoProvider 로 감싸져있지 않다면 에러를 발생시키도록 커스텀 Hook 을 수정하자.
꼭 이렇게 해줄 필요는 없지만, Context 사용을 위한 커스텀 Hook 을 만들 때 이렇게 에러 처리를 해준다면, 나중에 실수를 하게 됐을 때 문제점을 빨리 발견 할 수 있다.
import React, { useReducer, createContext, useContext, useRef } from 'react'; const initialTodos = [ { id: 1, text: '프로젝트 생성하기', done: true }, { id: 2, text: '컴포넌트 스타일링하기', done: true }, { id: 3, text: 'Context 만들기', done: false }, { id: 4, text: '기능 구현하기', done: false } ]; 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}`); } } const TodoStateContext = createContext(); const TodoDispatchContext = createContext(); const TodoNextIdContext = createContext(); export function TodoProvider({ children }) { const [state, dispatch] = useReducer(todoReducer, initialTodos); const nextId = useRef(5); return ( <TodoStateContext.Provider value={state}> <TodoDispatchContext.Provider value={dispatch}> <TodoNextIdContext.Provider value={nextId}> {children} </TodoNextIdContext.Provider> </TodoDispatchContext.Provider> </TodoStateContext.Provider> ); } export function useTodoState() { const context = useContext(TodoStateContext); if (!context) { throw new Error('Cannot find TodoProvider'); } return context; } export function useTodoDispatch() { const context = useContext(TodoDispatchContext); if (!context) { throw new Error('Cannot find TodoProvider'); } return context; } export function useTodoNextId() { const context = useContext(TodoNextIdContext); if (!context) { throw new Error('Cannot find TodoProvider'); } return context; }
App.js
최상단에서 <TodoProvider>로 감싸준다.
function App() { return ( <> <TodoProvider> <GlobalStyle /> <TodoTemplate> <TodoHead /> <TodoList /> <TodoCreate /> </TodoTemplate> </TodoProvider> </> ); }
components/TodoHead.js
TodoHead 컴포넌트에서 useTodoState 를 사용해보자.
import React from 'react'; import styled from 'styled-components'; import { useTodoState } from '../TodoContext'; const TodoHeadBlock = styled.div` padding-top: 48px; padding-left: 32px; padding-right: 32px; padding-bottom: 24px; border-bottom: 1px solid #e9ecef; h1 { margin: 0; font-size: 36px; color: #343a40; } .day { margin-top: 4px; color: #868e96; font-size: 21px; } .tasks-left { color: #20c997; font-size: 18px; margin-top: 40px; font-weight: bold; } `; function TodoHead() { const todos = useTodoState(); console.log(todos); return ( <TodoHeadBlock> <h1>2019년 7월 10일</h1> <div className="day">수요일</div> <div className="tasks-left">할 일 2개 남음</div> </TodoHeadBlock> ); } export default TodoHead;
반응형'📌 React' 카테고리의 다른 글
[React] TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined 에러 (0) 2022.10.14 [React] Todolist 만들기 정리(3) (0) 2022.09.29 [React] Todolist 만들기 정리(1) (0) 2022.09.28 [React] Context API (0) 2022.09.27 [React] 데이터를 저장하는 방법 (0) 2022.09.27