
useReducer()는 useState()와 같은 상태 관리, 상태 업데이트 훅이다.
변경할 값이 많을 때, 즉 상태 관리할 데이터가 많아질 때 주로 많이 사용한다.
좀 더 구조화된 방식으로 상태를 관리하고 싶을 때 사용할 수 있다.
const [state,dispatch] = useReducer(reducer,initialState);
state : 상태이름
dispatch : 상태를 변경할 때 필요한 정보를 전달하는 '함수'(주문서)
reducer : dispatch를 확인해서 state를 변경해주는 '함수'
initialState : state에 전달할 초기 값
1.useState()
2.useReducer()
const [todos, dispatch] = useReducer(reducer, mockData);
function reducer(state, action) {
switch (action.type) {
case 'CREATE':
return [action.data, ...state];
case 'UPDATE':
return state.map((item) =>
item.id === action.targetId ? { ...item, isDone: !item.isDone } : item
);
case 'DELETE':
return state.filter((item) => item.id !== action.targetId);
default:
return state;
}
}
위는 내가 작성한 Todo App의 reducer 함수 정의 부분이다
reducer 함수는 두 개의 인수를 받는다. state (현재 상태) 와 action 액션 객체
action.type 에 따라 다른 상태 변경 로직을 실행한다.
예를 들면 CREATE 는 새 할일을 상태(state)에 추가하는 부분인데. action.type 이 CREATE로 들어오게 되면, 새로운 할 일을 기존의 state 배열 앞에 추가하고 새로운 배열을 반환한다.
그리고 onCreate 함수는 이렇게 작성하였다.
const onCreate = (content) => {
dispatch({
type: 'CREATE',
data: {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
},
});
};
새 할 일을 나타내는 객체를 생성하고 CREATE 액션과 함께 dispatch를 호출한다. dispatch 함수에 이렇게 타입과 data를 객체 형태로 선언할 수 있다.
import { useReducer } from 'react';
const mockData = [
{ id: 0, isDone: false, content: 'React 공부하기', date: new Date().getTime() },
{ id: 1, isDone: false, content: '빨래', date: new Date().getTime() },
{ id: 2, isDone: false, content: '운동', date: new Date().getTime() },
];
function reducer(state, action) {
switch (action.type) {
case 'CREATE':
return [action.data, ...state];
case 'UPDATE':
return state.map((item) =>
item.id === action.targetId ? { ...item, isDone: !item.isDone } : item
);
case 'DELETE':
return state.filter((item) => item.id !== action.targetId);
default:
return state;
}
}
function App() {
const [todos, dispatch] = useReducer(reducer, mockData);
const onCreate = (content) => {
dispatch({
type: 'CREATE',
data: {
id: Date.now(),
isDone: false,
content: content,
date: new Date().getTime(),
},
});
};
const onUpdate = (targetId) => {
dispatch({ type: 'UPDATE', targetId });
};
const onDelete = (targetId) => {
dispatch({ type: 'DELETE', targetId });
};
return (
<div>
{/* UI Components here */}
</div>
);
}
useReducer를 사용함으로써 상태 관리 로직이 reducer 함수 하나에 집약되어 있으며, 상태 업데이트가 모두 dispatch 함수를 통해 일관되게 이루어진다는 것을 알 수 있다.2. useReducer를 사용하지 않고 useState만 사용한다면?
import { useState, useRef } from 'react';
const mockData = [
{ id: 0, isDone: false, content: 'React 공부하기', date: new Date().getTime() },
{ id: 1, isDone: false, content: '빨래', date: new Date().getTime() },
{ id: 2, isDone: false, content: '운동', date: new Date().getTime() },
];
function App() {
const [todos, setTodos] = useState(mockData);
const idRef = useRef(3);
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
};
const onUpdate = (targetId) => {
setTodos(
todos.map((item) =>
item.id === targetId ? { ...item, isDone: !item.isDone } : item
)
);
};
const onDelete = (targetId) => {
setTodos(todos.filter((item) => item.id !== targetId));
};
return (
<div>
{/* UI Components here */}
</div>
);
}
코드 분리와 가독성 측면
useReducer : redecer 함수에 모든 상태 변경 로직이 정의되어 있다, 이로 인해 상태 관리 로직이 한 곳에 집중되어 있으며, 상태가 어떻게 변경되는지를 쉽게 파악할 수 있다. 또한 상태 업데이트가 모두 dispatch 를 통해 일관되게 이루어지므로 코드 가독성과 유지보수성이 높아진다.
useState: 상태 변경 로직이 onCreate, onUpdate, onDelete 함수 내부에 직접 구현되어 있다. 상태 변경 로직이 분산되어 있어, 시간이 지남에 따라 코드의 복잡성이 증가하고 유지보수가 어려워질 수 있다.
상태 업데이트 방식
useReducer: dispatch를 사용하여 상태 업데이트 요청을 보냅니다. 이 방식은 상태 변경을 명확하고 예측 가능하게 만들어 줍니다. 또한, action 객체를 사용하여 상태 변경에 필요한 추가적인 정보를 전달할 수 있습니다.
useState: 상태를 직접 변경하기 위해 setTodos를 호출합니다. 이 방식은 간단한 상태 변경에는 적합하지만, 복잡한 상태 변경 로직이 필요한 경우 코드가 지저분해질 수 있습니다.