ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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;

     

    결과 화면

     

     

     

     

     

     

    반응형

    댓글

Designed by Tistory & Awesome Soo © 2022 All rights reserved.