상태에 어떤 변화가 필요하다면 액션(actions)이라는 것이 발생한다. 액션 객체는 다음과 같은 형식으로 이루어져 있다. 액션 객체는 반드시 type 필드를 가지고 있어야 하고 이를 액션의 이름이라고 생각하면 된다. 그 이외의 값들은 상태 업데이트를 할 때 필요한 값이다.
{
type: 'ADD_USER',
user: {
id: 1,
name: '아갈인파이터',
age: 25
}
}
액션 생성 함수(action creator)는 액션 객체를 만들어주는 함수이다.
const addUser = (user) => ({
type: 'ADD_USER',
user,
});
리듀서(reducer)는 새로운 상태를 반환하여 상태를 업데이트(상태 변화)를 일으키는 함수이다. 액션을 만들어 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아온다. 그리고 새로운 상태를 만들어 반환한다. 이때 상태는 불변성을 유지해야한다!
const initialState = {
count: 1
};
const reducer =(state = initialState, action) => {
switch(action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
디스패치(dispatch)는 액션을 발생시키는 것이다. 이 함수는 dispatch(action) 과 같은 형태로 액션 객체를 파라미터로 넣어서 호출한다. 디스패치로 액션을 발생시키면 리듀서가 액션에 따라 새로운 상태를 반환한다.(상태 업데이트)
dispatch({ type: 'INCREMENT' });
import { useReducer } from 'react';
const reducer = (state, action) => {
switch(action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer({ count: 0 });
return (
<>
<h1>Count: {state.count}</h1>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</>
);
};
export default Counter;

다음 예제 코드에는 좋지 않은 코드가 있다. 여기서는 useReducer의 사용법만 간단하게 알아보자.
import { useRef, useReducer } from 'react';
const reducer = (state, action) => {
switch(action.type) {
case 'ADD_USER':
return state.concat(action.user);
case 'REMOVE_USER':
return state.filter((user) => user.name !== action.userName);
default:
return state;
}
};
const UserList = () => {
const [state, dispatch] = useReducer(reducer, []);
const userNameRef = useRef(null);
const userAgeRef = useRef(null);
const onAddUser = () => {
const user = {
name: userNameRef.current.value,
age: parseInt(userAgeRef.current.value)
}
dispatch({ type: 'ADD_USER', user });
};
const onRemoveUser = (userName) => {
dispatch({ type: 'REMOVE_USER', userName });
};
return(
<>
이름: <input type='text' ref={userNameRef} />
나이: <input type='number' ref={userAgeRef} />
<button onClick={onAddUser}>추가</button>
<ul>
{state.map(({ name, age }, i) => (
<li key={i}>
{name}({age})
<button onClick={() => onRemoveUser(name)}>삭제</button>
</li>
))}
</ul>
</>
);
};
export default UserList;
😨 useReducer의 사용법을 간단히 알아보기 위해 위와 같은 코드를 작성했지만, 좋지 못한 코드의다. 첫 번째로, onRemoveUser에서 삭제할 유저의 조건을 이름으로 지정했다. 중복된 이름이 있다면 모두 삭제될 것이다. 이럴때는 user 객체에 id를 추가하는 것이 좋다.
그리고 두 번째로, 반복되는 li의 key를 배열의 인덱스로 주고있다. 유저가 추가되고 삭제됨에 따라 인덱스는 변한다. 이 역시 user에 id를 추가하고 key를 id로 주는 것이 바람직하다!!😨
