useReducer
이전까지 상태를 업데이트 할 때는 useState를 사용해서 새로운 상태를 설정해주었는데, 상태를 관리할 때 useState를 사용하는 것 말고도 다른 방법이 있다. 바로 useReducer을 사용하는 것인데, useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트해 주고 싶을 때 사용하는 Hook이다. 이 Hook 함수를 사용하면 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다. 리듀서는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수이다. 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜야 한다.
function reducer(state,action){
return {
//불변성을 지키면서 업데이트한 새로운 상태를 반환한다.
};
}
액션 값은 주로 다음과 같은 형태로 이루어져 있다.
{
type:'INCREMENT',
//다른 값들이 필요하다면 추가로 들어감
}
카운터 구현하기
import logo from './logo.svg';
import { useState, useEffect } from 'react';
import { useReducer } from 'react';
function reducer(state, action){
//action.type에 따라 다른 작업 수행
switch(action.type){
case 'INCREMENT':
return { value: state.value+1};
case 'DECREMENT':
return {value: state.value-1};
default:
return state;
}
}
const Counter=()=>{
const [state, dispatch] =useReducer(reducer, {value:0});
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다.
</p>
<button onClick={()=> dispatch({type:'INCREMENT'})}>+1</button>
<button onClick={()=>dispatch({type:'DECREMENT'})}>-1</button>
</div>
)
};
export default Counter;
- useReducer(reducer, {value:0})useReducer의 첫 번째 파라미터에는 리듀서 함수(reducer)를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값(value=0)을 넣어 준다.
- [state, dispatch]여기서 state는 현재 가리키고 있는 상태이고, dispatch는 액션을 발생시키는 함수이며 dispatch(action)과 같은 형태이다. 함수 안에 파라미터로 액션 값을 넣어 주면 리듀서 함수가 호출되는 구조이다.
useReducer를 사용했을 때의 가장 큰 장점은 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 점이다.
import logo from './logo.svg';
import { useState, useEffect } from 'react';
import { useReducer } from 'react';
const App=()=>{
return <Counter />;
};
export default App;
이렇게 컴포넌트를 분리해도 이전과 마찬가지로 잘 동작한다.
인풋 상태 관리하기
이번에는 useReducer를 사용해서 Info 컴포넌트에서 인풋 상태를 관리해보겠다. 기존에는 인풋이 여러 개면 useState를 여러 번 사용하는 방식으로 구현하였는데, useReducer를 사용하면 기존에 클래스형 컴포넌트에서 input 태그에 name 값을 할당하고 e.target.name 을 참조하여 setState를 해 준 것과 유사한 방식으로 작업을 처리할 수 있다.
import React, { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, {
name: '',
nickname: ''
});
const { name, nickname } = state;
const onChange = e => {
dispatch(e.target);
};
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임:</b> {nickname}
</div>
</div>
);
}
export default Info;
- useReducer에서의 액션은 어떤 값도 사용이 가능하다. 그래서 예제에서는 이벤트 객체가 지니고 있는 e.target 값 자체를 액션 값으로 사용하였다. 이런식으로 인풋을 관리하면 아무리 인풋의 갯수가 많아져도 코드를 짧고 깔끔하게 유지할 수 있다.
useReducer vs useState
그렇다면 어떨 때 useReducer를 쓰고 어떨 때 useState를 써야할까?
const [value, setValue] = useState(true);
위의 코드와 같이 컴포넌트에서 관리하는 값이 딱 하나이고, 그 값이 단순한 숫자, 문자열 또는 boolean 값이면 확실히 useState로 관리하는 것이 편리하다.
하지만 만약, 컴포넌트에서 관리하는 값이 여러개여서 상태의 구조가 복잡해진다면 useReducer로 관리하는 것이 편하다.
function formReducer(state, action) {
switch (action.type) {
case 'updateField':
return {
...state,
[action.field]: action.value
};
default:
return state;
}
}
const UserForm = () => {
const [state, dispatch] = useReducer(formReducer, {
name: '',
age: '',
email: ''
});
const handleChange = e => {
dispatch({
type: 'updateField',
field: e.target.name,
value: e.target.value
});
};
'리액트' 카테고리의 다른 글
[React] useCallback이란? (0) | 2024.01.22 |
---|---|
[React] useMemo란? (2) | 2024.01.18 |
[React] useEffect란? (0) | 2024.01.15 |
[React] State란? (3) | 2024.01.11 |
[React] React Router : Router, Link (2) | 2024.01.08 |