Context
값을 동적으로 업데이트해야 하는 경우를 알아본다. 일정관리 애플리케이션을 예시로 들어 작성했다.
✨ component
TodoTemplate
: 하얀 배경색의 전체 박스
TodoHead
: 오늘 날짜와 앞으로 할 일의 개수를 보여줌.
TodoList
: 여러 개의 TodoItem
을 렌더링함.
TodoItem
: 각 할 일에 대한 정보를 렌더링함. 토글 기능이 있는 체크 버튼과 삭제 기능이 있는 휴지통 아이콘이 있음. (마우스를 올려야 휴지통 아이콘이 보인다.)
TodoCreate
: 새로운 할 일을 등록하는 컴포넌트이다. 플러스 아이콘이 있는 버튼을 누르면 할 일을 입력할 수 있는 폼이 나타난다. 버튼을 다시 누르면 폼이 사라진다.
✨ 전역 상태 관리
- 전역적으로 봤을 때 필요한
state
: 할 일에 대한 정보를 담은 배열
const todos = [
{
id: 1,
text: "React 공부하기",
done: false
},
{
...
},
...
];
- 여러 컴포넌트에서
todos
가 필요로 함으로 최상위 컴포넌트인 App
의 state
로 담아 필요한 자식 컴포넌트에게 props
로 전달한다.
- 토글 기능, 삭제 기능, 생성 기능을 담은
setState
함수를 필요로 하는 함수도 App
컴포넌트에서 만들어 함수들을 이벤트 등록에 필요로 하는 자식 컴포넌트에게 props
로 전달한다.
- 이번 예시의 경우 컴포넌트의 구조가 복잡하지 않아
App
의 state
를 props
로 전달하는데 큰 무리가 없다.
- 하지만, 컴포넌트의 구조가 깊어지고 복잡해질수록 여러 컴포넌트를 거쳐
state
를 전달해야 한다.
- 프로젝트의 규모가 커질수록 최상위 컴포넌트인
App
에서 모든 상태 관리를 하기엔 무리가 있다.
- 그래서 전역적으로 관리하거나 필요로 하는 상태가 있을 경우 상태 관리 라이브러리를 이용하거나
Context API
를 활용한다.
💡 Context API를 이용한 상태 관리
const initialTodos = [
{
id: 1,
text: "React 공부하기",
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 TodoContext = createContext({
todos: [],
dispatch: null,
nextId: 2
});
function TodoProvider({children}){
const [state, dispatch] = useReducer(todoReducer, initialTodos);
const nextId = useRef(2);
const value = {
todos: state,
dispatch,
nextId
};
return (
<TodoContext.provider value={value}>
{children}
</TodoContext.provider>
);
}
export { TodoProvider };
export default TodoContext;
function App() {
return (
<TodoProvider>
<TodoTemplate>
<TodoHead/>
<TodoList/>
<TodoCreate/>
</TodoTemplate>
</TodoProvider>
);
}
createContext
함수를 호출하여 TodoContext
생성
TodoProvider
라는 함수형 컴포넌트는 TodoContext
에 있는 값을 value props
로 설정한 값으로 바꾸고 자식 컴포넌트에게 value props
값을 전달한다.
value prop
에는 todos
상태값, todoReducer
를 호출할 수 있는 dispatch
함수, 새로운 항목을 추가할 때 사용할 nextId
가 있다.
TodoContext
에 있는 값을 각 컴포넌트에서 사용하기 위해 useContext
를 사용할 것이다.
TodoHead
const TodoHead = () => {
const { todos } = useContext(TodoContext);
const undoneTasks = todos.filter(todo => !todo.done).length;
}
TodoCreate
const TodoCreate = () => {
cosnt { dispatch, nextId } = useContext(TodoContext);
const [ input, setIntput] = useState("");
const onChange = useCallback((e) => {
setInput(e.target.value);
}, []);
const onCreate = useCallback((e) => {
e.preventDefault();
const todo = {
id: nextId.current,
text: input,
done: false
};
dispatch({
type: "CREATE",
todo
});
setInput("");
nextId.current += 1;
}, [input]);
}
TodoList
const TodoList = () => {
const { todos } = useContext(TodoContext);
return (
<ul>
{todos.map(todo =>
<TodoItem
key={todo.id}
done={todo.done}
text={todo.text}
id={todo.id}/>
}
</ul>
)
}
TodoItem
const TodoItem = ({id, text, done}) => {
const { dispatch } = useContext(TodoContext);
const onToggle = useCallback((id) => {
dispatch({
type: "TOGGLE",
id
});
}, []);
const onRemove = useCallback((id) => {
dispatch({
type: "REMOVE",
id
});
}, []);
}
💡 Context API와 localStorage
- 새로고침을 하면
todos
에 있던 내용들이 초기화된다.
- 새로고침을 해도
todos
에 있던 내용들을 기억할 수 있도록 localStorage
에 todos
상태값을 저장한다.
todos
상태값이 업데이트될 때마다 업데이트된 todos
를 localStorage
에 저장하고 초기 렌더링 시 localStorage
에 저장된 todos
를 불러와 상태값으로 설정한다.
const KEY = "todos";
function TodoProvider({children}){
const [state, dispatch] = useReducer(todoReducer, [], () => {
const todos = JSON.parse(localStorage.getItem(KEY));
return todos ? todos : [];
});
useEffect(() => {
localStroage.setItem(KEY, JSON.stringfy(state));
}, [state]);
const nextId = useRef(1);
const value = {
todos: state,
dispatch,
nextId
};
return (
<TodoProvider value={value}>
{children}
</TodoProvider>
);
}
localStorage API
이용
localStorage.getItem(KEY)
localStorage.setItem(KEY, item)
useEffect
를 이용하여 초기 렌더링한 이후와 상태값이 업데이트 된 이후에 상태값을 localStorage
에 저장하도록 했다.
useReducer
의 두 번째 인자로는 빈 배열을 전달하고, 세 번째 인자로 초기 상태값을 설정하는 함수를 전달했다.
참고: 벨로퍼트와 함께하는 모던 리액트