React - TodoList (2)

김정욱·2020년 10월 4일
0

React

목록 보기
9/22
post-custom-banner

사용 개념

  • styled-components를 통한 컴포넌트 스타일링 (이전 게시글)
  • Context API를 사용한 전역 상태 관리 (이번 게시글!)

Context API

  • [ 이전 Stae 관리 ]
    : 상위 컴포넌트에서 하위 컴포넌트State를 전달하기 위해 props로 지정해서 계속 넘겨줘야 했다.
       (거쳐가는 컴포넌트가 많을 수록 비효율적)

  • [Context API 사용]
    : Context를 사용해서 전역 범위로 state를 관리할 수 있기 때문에 각 컴포넌트에서 Direct Access 가능

사용하기

1단계) reducer 작성 + context 생성

  • ( 전체 코드 )
<TodoContext.js>

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>
  );
}

  • ( reducer 작성 부분 )
<TodoContext.js>
...
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}`);
  }
}
...

-각 action에 맞는 연산을 수행해서 state값 변경
-불변성을 지키면서 state를 변경하는 것이 Point!
-실제 서버와 연결하여 로직이 많아지면 별도 action파일로 분리!


  • ( context 생성 및 사용 부분 )
<TodoContext.js>
...
       /* Context 생성 */
const TodoStateContext = createContext();
const TodoDispatchContext = createContext();
const TodoNextIdContext = createContext();

export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialTodos);
  const nextId = useRef(5);
  return (
             /* Context 사용 */
    <TodoStateContext.Provider value={state}>
      <TodoDispatchContext.Provider value={dispatch}>
        <TodoNextIdContext.Provider value={nextId}>
          {children}
        </TodoNextIdContext.Provider>
      </TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}
...

-Context를 3개로 분리하였음 (State 용도 / Dispatch 용도 / netxId 용도)
-Context는 생성 후 .Provider 태그로 사용
-각 Context는 valuestate와 dispatch를 지정


2단계) 커스텀 Hook 만들기 + 예외처리

  • ( 커스텀 미 적용 호출 방법 )
import React, { useContext } from 'react';
import { TodoStateContext, TodoDispatchContext } from '../TodoContext';

function Sample() {
  const state = useContext(TodoStateContext);
  const dispatch = useContext(TodoDispatchContext);
  return <div>Sample</div>;
}

: useContext로 해당 Context를 직접 넣어줘야 한다


  • ( 커스텀 적용 및 호출방법 )
<TodoContext.js> - 커스텀 적용
...
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;
}

<커스텀 hook 호출 방법 >

import React from 'react';
import { useTodoState, useTodoDispatch } from '../TodoContext';

function Sample() {
  const state = useTodoState();
  const dispatch = useTodoDispatch();
  return <div>Sample</div>;
}

: 커스텀에서 이미 useContext로 Context를 넣었기 때문
  커스텀 함수 이름으로 바로 사용


3단계) Component 감싸기

<App.js>
import { TodoProvider } from './TodoContext';
...
function App() {
  return (
    <TodoProvider>
      <GlobalStyle />
      <TodoTemplate>
        <TodoHead />
        <TodoList />
        <TodoCreate />
      </TodoTemplate>
    </TodoProvider>
  );
}

: 태그들을 감싸줘야 그 내부에서 사용 가능


4단계) 컴포넌트에서 사용하기

import { useTodoState } from '../TodoContext';
import { useTodoDispatch } from '../TodoContext';
...
 /* 커스텀 적용 */ -- 커스텀 한 함수로 바로 사용 가능
 const todos = useTodoState();
 const dispatch = useTodoDispatch();

 /* 커스텀 미적용 */ -- Context자체를 useContext의 인자로 넣음
 const todos = useContext(TodoStateContext);
 const dispatch = useContext(TodoDispatchContext);

[ 결과물 ]


https://react-todolists.vercel.app/

profile
Developer & PhotoGrapher
post-custom-banner

0개의 댓글