[React] 투두리스트 웹 만들기 2
기능을 구현하려면 여러 상태들을 관리해야하는데
보통은 useState로 부모 클래스에서 관리하곤 합니다.
이번에는 ContextAPI를 이용해서 보다 깔끔하게 상태를 관리해 보겠습니다. (벨로퍼트님의 강좌를 참고하였습니다.)
TodoContext라는 파일을 하나 만들어 주고
useReducer를 사용해서 상태를 관리해 줍니다.
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);
}
Reducer 는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수입니다.
switch문을 사용해서 각각 케이스의 경우 새로운 상태를 반환합니다.
Create의 경우 새로운 todo를 추가하여 상태를 반환하고,
Toggle의 경우 현재 상태를 맵핑하여 todo.id가 action.id와 일치하면 todo를 복사하여
todo의 상태를 반전하여 반환하고, 그렇지 않다면 todo의 상태를 그대로 반환합니다.
Remove의 경우 해당 id를 제외한 새로운 todo의 상태를 반환합니다.
TodoProvider 컴포넌트의 경우 자식 컴포넌트들에게 상태와 디스패치를 제공함으로써
접근할 수 있게 해줍니다.
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);
}
다음은 NextId라는 값을 추가해 줍니다.
이 값은 새로운 할 일을 추가할 때 갖게 되는 고유의 id입니다.
이 때는 useRef를 사용해서 특정 DOM에 쉽게 접근하게 해줍니다.
useRef는 변화는 감지하지만 렌더링은 하지 않게 합니다.
또한 input에 focus할 때 사용하기도 합니다.
mport 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;
TodoHeader에서 useTodoState컴포넌트를 이용해서 상태가 잘 전달되었는지 확인해 봅시다.
경로를 잘못 입력한 것 같습니다.
TodoContext의 경로가 잘못 되었네요.
하지만 30분 동안 경로를 수정해도 해결되지 않았다던..
다음 시간에 이어서 해보도록 하겠습니다.
