state를 변경할 때 주로 useState를 사용하였는데 아래의 경우에 관리하기 힘들다고 느꼈다.
- state 가 늘어날수록
- 객체가 중첩될수록
- state 가 자주 변경될수록
내가 주로 관리하던 state는 다음과 비슷한 형태를 가지는데
const [state, setState] = useState({
arrTodos: [
{ code: 0, hobby: '그림그리기' },
{ code: 1, hobby: '운동하기' },
],
objUser: {
num: 1000,
name: '김영희',
},
strTitle: '', // 추가 input 제목
strContent: '', // 추가 input 내용
});
이번에 맡은 일이 input도 많고 state만 60개가 넘어가서 기존의 방식으로는
안전하게 state를 관리하기 힘들었다 (useState로 코드 작성 시 너무 길어지기도 했다)
이럴 땐 useReducer가 효과적이라길래 useReducer로 결정했다!
useReducer 사용하는 경우
예제는 예전에 만든 간단한 투두리스트이다.
const [state, setState] = useState({
title: '', // 추가 input 제목
content: '', // 추가 input 내용
});
const handleChange = ({ target }) => {
const { name, value } = target;
setState((prev) => ({ ...prev, [name]: value }));
};
<div>
제목 :
<input
type="text"
name="title"
value={state.title}
onChange={handleChange}
required
style={{
outline: 'none',
display: 'inline-block',
}}
/>
</div>
<div>
내용 :
<input
type="text"
name="content"
value={state.content}
onChange={handleChange}
required
/>
</div>
사용자가 내용을 입력할 때 마다 handleChange가 호출되어 input의 value가 변경되는 로직이다.
state가 2개 밖에 없어서 useState를 사용해도 전혀 문제없지만 연습용으로 해당 예제를 useReducer로 변경해보았다.
const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn);
state
dispatch
reducerFn
initialState
initFn
initialState
const initialInputState = {
title: '',
content: '',
};
reducerFn
const stateReducer = (state, action) => {
if (action.type === 'USER_INPUT') {
return {
...state,
[action.field]: action.payload,
};
}
return state;
};
useReducer
최상단에 import 해야된다.
import { useState, useEffect, useReducer } from 'react';
const [formState, dispatch] = useReducer(stateReducer, initialInputState);
이렇게 작성하면 초기 설정은 끝났다.
이제 액션을 디스패치하는 함수를 만들어서 input에 입력된 값을 변경해보겠다.
const handleTextChange = ({ target }) => {
const { name, value } = target;
dispatch({
type: 'USER_INPUT',
field: name,
payload: value,
});
};<div>
제목 :
<input
type="text"
name="title"
value={formState.title}
onChange={handleTextChange}
required
style={{
outline: 'none',
display: 'inline-block',
}}
/>
</div>
<div>
내용 :
<input
type="text"
name="content"
value={formState.content}
onChange={handleTextChange}
required
/>
</div>
<div>
- 사용자가 input에 값을 입력하면 handleTextChange 가 호출된다.
- handleTextChange 함수에서 액션 디스패치 및 새로운 값을 전달한다.
- 리듀서 함수에서 조건에 해당하는 액션이 실행되고 새로운 값이 반환된다.
input의 내용을 각각 변경하는 경우는 위 코드 처럼 사용하면 된다.
그러나...
API를 호출해서 여러 데이터를 가져온 다음
여래 개의 input 값을 동시에 변경해야 하는 경우는 코드를 어떻게 작성해야 효율적일까?
const handleAllChange = () => {
const title = { title: '운동하기' };
const content = { content: '러닝' };
const mergeData = { ...title, ...content };
dispatch({
type: 'MULTIPLE_DATA',
payload: mergeData,
});
};
handleAllChange 함수를 만들고 예시 데이터로 title과 content를 선언한 다음 합친다.
❗ 주의할 점은 로컬 상수의 property가 initialState 의 property와 동일해야 된다.
if (action.type === 'USER_INPUT') {
return {
...state,
[action.field]: action.payload,
};
}
if (action.type === 'MULTIPLE_DATA') {
return {
...state,
...action.payload,
};
}
"MULTIPLE_DATA" 액션이 디스패치되는데 "USER_INPUT" 과 차이점은
속성을 정의하지 않고 전개 문법을 이용하여 최신 state와 합쳐주면 된다.
(이전에 handleAllChange 함수에서 속성을 정의하였기 때문)
state를 초기화하고 싶은 경우는 가장 간단하다.
액션만 디스패치하고
dispatch({
type: 'RESET',
})
initialInputState를 리턴하면 된다.
if (action.type === 'RESET') {
return initialInputState;
}
input에 유효성 검증이 필요한 경우
액션을 디스패치하기 전 유효성 검증 로직을 추가하고 해당 결과를 payload로 전달하면 되는데
작성하다 보니 너무 길어져서 이 부분은 다음에 정리해야 겠다.
[GitHub] To-Do List
useReducer Form Example
【한글자막】 React 완벽 가이드 with Redux, Next.js, TypeScript
멋쪄용 👍 잘보고갑니다.