useReducer : 상태를 업데이트하는 Hook
useState
setValue(5);
useReducer
const [state, dispatch] = useReducer(reducer, initialState);
Action 객체를 기반으로 상태를 업데이트한다.
Action 객체 : 상태를 업데이트 할 때 참조하는 객체
type을 사용해서 어떤 업데이트를 진행할 것인지 명시할 수 있고, 업데이트할 때 참조할 다른 값을(diff) 객체 안에 넣을 수 있다.
만약 컴포넌트에서 관리하는 값이 1개이고 그 값이 단순하다면(문자열, 숫자, 불리언 등) useState로 관리하는 것이 용이하다.
하지만 컴포넌트에서 관리하는 값이 여러 개고 상태 구조가 복잡해진다면 useReducer가 더 편리할 것이다.
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
const [number, dispatch] = useReducer(reducer, 0);
useReducer(reducer, 0);
: reducer(reducer 함수), 0(초기값)import { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
throw new Error("unhandled action");
}
}
function Counter() {
const [number, dispatch] = useReducer(reducer, 0);
const onIncrease = () => {
dispatch({
type: "INCREMENT",
});
};
const onDecrease = () => {
dispatch({
type: "DECREMENT",
});
};
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
App.js
import { useRef, useReducer, useMemo, useCallback } from "react";
import UserList from "./UserList";
import CreateUser from "./CreateUser";
function countActiveUsers(users) {
console.log("counting active users...");
return users.filter((user) => user.active).length;
}
const initialState = {
inputs: {
username: "",
age: "",
},
users: [
{ id: 1, username: "bae", age: 22, active: true },
{ id: 2, username: "lee", age: 21, active: false },
{ id: 3, username: "lim", age: 25, active: false },
],
};
function reducer(state, action) {
switch (action.type) {
case "CHANGE_INPUT":
return {
// 기존 상태를 넣고 inputs 값을 덮어씌워서 불변성을 유지한다.
...state,
inputs: {
...state.inputs,
[action.name]: action.value,
},
};
case "CREATE_USER":
return {
// 작성된 내용 input에서 삭제
inputs: initialState.inputs,
users: state.users.concat(action.user),
};
case "TOGGLE_USER":
return {
...state,
users: state.users.map((user) =>
user.id === action.id ? { ...user, active: !user.active } : user
),
};
case "REMOVE_USER":
return {
...state,
users: state.users.filter((user) => user.id !== action.id),
};
default:
throw new Error("Unhandled action");
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const { users } = state;
const { username, age } = state.inputs;
const nextId = useRef(4);
const onChange = useCallback((e) => {
const { name, value } = e.target;
dispatch({
type: "CHANGE_INPUT",
name,
value,
});
}, []);
const onCreate = useCallback(() => {
dispatch({
type: "CREATE_USER",
user: {
id: nextId.current,
username,
age,
},
});
nextId.current += 1;
}, [username, age]);
const onToggle = useCallback((id) => {
dispatch({
type: "TOGGLE_USER",
id,
});
}, []);
const onRemove = useCallback((id) => {
dispatch({
type: "REMOVE_USER",
id,
});
}, []);
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
age={age}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onToggle={onToggle} onRemove={onRemove} />
<div>활성 사용자 수 : {count}</div>
</>
);
}
export default App;