useState 처럼 state를 생성하고 관리할 수 있도록 하는 hook
이다.
여러개의 하위값을 포함하는 복잡한 state
를 다뤄야 할 때 useReducer
을 사용한다.
ex)
{
teacher: "Kim",
students: ["Kim", "Lee", "Shin"],
count: 3,
locations: [
{country: 'Korea', name: 'A'},
{country: 'Japan', name: 'B'}
]
}
가장 중요한 것은
state를 업데이트하는 로직 부분
을 해당컴포넌트로부터 분리
시키는 것을 가능하게 해준다.
useState로 state들을 관리하는 경우
- 지금은
컴포넌트 안
에 state 업데이트 로직이 포함되어 있다.
import { useState } from 'react';
function Counter() {
const [number, setNumber] = useState(0);
**const onDecrease = () => {
setNumber((prev) => prev - 1);
}
const onIncrease = () => {
setNumber((prev) => prev + 1);
}**
return (
<div>
<h1>Count : {number}</h1>
<button onClick={onDecrease}>-</button>
<button onClick={onIncrease}>+</button>
</div>
);
}
export default Counter;
useReducer을 활용하여 state들을 관리하는 경우
-
state 업데이트 로직이
Counter 컴포넌트 밖에 존재
한다. -
이렇게
컴포넌트 외부에 state 업데이트 로직을 분리시키는 것이 가능
하고,파일을 분리하는 것도 가능
하다.import { useReducer, useState } from 'react'; **function reducer(state, action) { switch (action.type) { case 'decrement': return state - 1; case 'increment': return state + 1; default: throw new Error(); } }** function Counter() { const [number, dispatch] = useReducer(reducer, 0); return ( <div> <h1>Count : {number}</h1> <button onClick={() => dispatch({ type: 'decrement'})}>-</button> <button onClick={() => dispatch({ type: 'increment'})}>+</button> </div> ); } export default Counter;
로직을 분리하면 좋은점?
- 다른 곳에서도 쉽게
재사용
이 가능하다는 점에서 최적화를 할 수 있다.
reducer을 알기 전에 다음 용어들을 알아야 한다.
Dispatch
: reducer에게 action을 요구하는 행위Action
: reducer에게 요청하는 내용Reducer
: state를 갱신하는 주체
쉽게 설명한 버전
- 사람은
은행
(Reducer)에게출금을 해달라고
(Action)요청하고
(Dispatch), 은행은거래 내역
(State)을 갱신한다.
컴포넌트 관점
Dispatch
함수의 인자로Action
이라는 행동을 넣어서 Reducer에게 전달한다.Reducer
은 우리가 넘겨준Action
에 맞게state
값을 갱신시킨다.
예제
import { useReducer, useState } from 'react';
function **reducer**(state, action) {
switch (action.type) {
case 'decrement':
return state - 1;
case 'increment':
return state + 1;
default:
throw new Error();
}
}
function Counter() {
const [number, **dispatch**] = **useReducer**(reducer, 0); // useReducer 기본 사용법
return (
<div>
<h1>Count : {number}</h1>
<button onClick={() => **dispatch**({ type: 'decrement'})}>-</button>
<button onClick={() => dispatch({ type: 'increment'})}>+</button>
</div>
);
}
export default Counter;
-
useReducer
함수기본 사용법
const [state, dispatch] = useReducer(reducer, initialState, init);
state
: 컴포넌트가 사용할 statedispatch
: reducer 함수를 실행시키며, 컴포넌트 내에서 state의 업데이트를 일으키기 위해 사용하는 함수reducer
: 컴포넌트 외부에서 state 업데이트 로직을 담당하는 함수- 현재 state와 action을 받아서, 업데이트 된 state를 반환
initialState
: 초기 stateinit
: 초기 함수
-
action
- 업데이트를 위한 정보를 가지고 있는 것이다.
- 따로 정해진 형태는 없으나, 주로
type
이라는key값
을 지닌 객체 형태로 사용된다.
기본 사용법
dispatch(**{ type: "decrement" }**)
- 여기서
{ type: "decrement" }
이 부분이 action 이다.
-
dispatch
함수- reducer 함수를 실행시키는 함수
기본 사용법
dispatch({ type: "decrement" });
-
reducer
함수- dispatch에 의해 실행되는 함수
- state를 업데이트 하는 함수
기본 사용법
function reducer(state, action) { switch (action.type) { case "decrement": // action의 type이 "decrement"일 때, 현재 state에서 1을 뺀 값을 반환함 return state - 1; case "increment": // action의 type이 "increment"일 때, 현재 state에서 1을 더한 값을 반환함 return state + 1; default: // 정의되지 않은 action type이 넘어왔을 때는 에러를 발생시킴 throw new Error("Unsupported action type:", action.type); } }
- 기존
age
는 42
function handleClick() {
console.log(state.age);
dispatch({ type: "incremented_age" });
console.log(state.age);
setTimeout(() => {
console.log(state.age);
}, 5000);
}
-
결과
function handleClick() { console.log(state.age); // 42 dispatch({ type: "incremented_age" }); // Request a re-render with 43 console.log(state.age); // Still 42! setTimeout(() => { console.log(state.age); // Also 42! }, 5000); }
-
이유
state가 스냅샷처럼 동작한다. state 상태를 업데이트하면 새 상태값으로 요청되지만, 이미 실행 중인 이벤트 핸들러의 Javascript 변수에는 영향을 미치지 않는다.
-
해결
직접 reducer을 호출하고 변수에 담음으로서 해결할 수 있다.
const action = { type: "incremented_age" }; dispatch(action); const nextState = reducer(state, action); console.log(state); // { age: 42 } console.log(nextState); // { age: 43 }
출처
https://velog.io/@iamhayoung/React-Hooks-useReducer에-대해-알아보기
https://ko.legacy.reactjs.org/docs/hooks-reference.html#usereducer
https://react.dev/reference/react/useReducer#adding-a-reducer-to-a-component