📌 useReducer 훅
React에서 제공하는 훅 중 useReducer라는 상태 관리 훅이 있다.
useState와 마찬가지로 상태를 관리하지만,
컴포넌트로부터 로직을 분리하기 때문에 관리해야하는 상태나 로직이 복잡한 상황에서
useState보다 컴포넌트를 좀 더 깔끔히 관리할 수 있다는 장점이 있다.
한 컴포넌트 내에서 State를 업데이트하는 로직 부분을
그 컴포넌트로부터 분리시키는 것을 가능하게 해준다는 것이다.
그렇게 useReducer는 State 업데이트 로직을 분리하여
컴포넌트의 외부에 작성하는 것을 가능하게 함으로써, 코드의 최적화를 이루게 해준다.
State 업데이트 로직을 또다른 파일에 작성해서(분리), 분리된 파일을 불러와서 사용하는 것도 가능
🔎 useReducer를 사용한 경우
컴포넌트 외부에 State 업데이트 로직이 존재
🔎 useState를 사용한 경우
컴포넌트 내부에 State 업데이트 로직이 존재
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
}
🤔 useReducer, useState 비교
- useState
- 관리해야 할 State가 1개일 경우
- 그 State가 단순한 숫자, 문자열 또는 Boolean 값일 경우
- 기존 상태를 고치거나, 추가하거나, 바꿔서 쓴다.
- useReducer
- 관리해야 할 State가 1개 이상, 복수일 경우
- 혹은 현재는 단일 State 값만 관리하지만, 추후 유동적일 가능성이 있는 경우
- 스케일이 큰 프로젝트의 경우
- State의 구조가 복잡해질 것으로 보이는 경우
- 기존 상태를 새 걸로 아예 바꾼다.
useState | useReducer |
컴포넌트 내부에 상태(state) 업데이트 로직이 존재 | 컴포넌트 외부에 상태(state) 업데이트 로직이 존재 |
관리해야할 상태(state)가 단순할 경우 | 관리해야할 상태(state)가 여러개인 경우 |
간단한 상태(state)로직을 다룰 때 사용 | 좀 더 복잡한 상태(state)로직을 다룰 때 |
상태(state)를 업데이트할 때 이전 상태(state)를 덮어쓴다. |
이전 상태(state)를 변경하지 않고 새로운 상태(state)를 생성 |
상태(state)가 단순한 숫자, 문자열 또는 boolean 값일 경우 |
현재는 단일 상태(state) 값만 관리하지만 추후 유동적일 가능성이 있는 경우 |
컴포넌트 내부에서 변경이 가능한 상태를 다룰 때 | 여러개의 연관된 상태를 함께 다룰 때 |
설정하고싶은 다음상태를 직접 지정해주는 방식으로 상태를 업데이트 setUsers(users => users.concat(user)) |
action이라는 객체를 기반으로 상태를 업데이트한다. *action 객체 : 업데이트할 때 참조하는 객체 dispatch({ type:"DECREMENT" }) |
컴포넌트에서 상태를 변경하면, 해당 컴포넌트가 다시 렌더링 된다. |
state와 action을 받아서 새로운 상태를 반환하는 reducer 함수를 사용 |
컴포넌트에서 상태가 변경되면 컴포넌트가 화면에 다시 그려져서 새로운 상태가 반영된다. |
스케일이 큰 프로젝트의 경우 |
사용자의 입력에 따라 컴포넌트가 동적으로 변경되어야 할 때 |
컴포넌트의 상태 업데이트 로직을 다른 파일로 분리하고 싶을 때 |
상태를 초기화 하거나 업데이트 할 때 | 다른 파일에서 reducer 함수를 정의하여 상태를 다룰 수 있다. |
📌 useReducer 구성요소
- useReducer 함수
- action 객체
- dispatch 함수
- reducer 함수
✅ useReducer 함수
const [<상태 객체>, <dispatch 함수>] = useReducer(<reducer 함수>, <초기 상태>, <초기 함수>);
import React, { useReducer } from "react";
const [state, dispatch] = useReducer(reducer, initialState, init);
- state : 컴포넌트에서 사용할 상태
- dispatch
reducer 함수를 실행시키며
컴포넌트 내에서 상태(state)의 업데이트를
일으키기 위해서 사용하는 함수 - reducer
useReducer 함수의 첫 번째 인자로
컴포넌트 외부에서 state를 업데이트하는 함수
로직을 담당하는 함수
(state, action) => {...} - initialState : 초기 상태값 state
- init
초기 상태를 정의하는 함수
2번째 인자인 initialState를 파라미터로 받는 함수
(initialArg) => {...}
✅ action 객체
action은 업데이트를 위한 정보를 가지고 있는 것이다.
dispatch의 인자가 되며 reducer함수의 두 번째 인자인 action에 할당된다.
dispatch함수에 행동(action)을 던지면, reducer함수가 이 행동(action)에 따라서 상태(state)를 변경
여기서 {type: "decrement"}이 action 객체이다.
dispacth({ type: "decrement" })
✅ dispatch 함수
reducer 함수를 실행시킨다.
dispatch 함수의 인자는 상태 업데이트를 위한 정보를 담은 action객체가 전달된다.
action을 통해 컴포넌트 내에서 state의 업데이트가 이루어진다.
📝 dispatch 함수 구조
현재 상태 값(state)과 사용자가 업데이트하고자 하는 정보(action)을 가지고 reducer 함수를 호출한다.
function dispatch(action) {
reducer(this.state, action)
}
✅ reducer 함수
dispatch 함수에 의해 실행된다.
컴포넌트 외부에서 state 상태 업데이트하는 로직을 담당
함수 인자로는 state와 action을 받게 된다.
state와 action을 활용하여 새로운 state를 반환한다.
useReducer 함수의 첫 번째 파라미터로 입력된 reducer함수는
현재상태(state)와 행동(action)은 인자로 받게 되는
이 action 값에 근거하여 기존의 state를 대체(replace)할 새로운 상태 state를 반환(return) 한다.
기존의 state를 새로운 state로 대체한다.
기존의 state를 변경하거나 추가하거나 덮어쓰지 않는다.
파라미터
- state : 현재 상태
- action : 사용자에 의해 수행되는 액션으로, dispath 함수를 호출할 때 넘기는 인자
- dispath({type: 'age', age: 10}) 를 통해 dispatch 함수를 호출하는 경우
{type: 'age', age: 10} 이 action이다. - 어떤 형식의 타입이든 올 수 있지만,
컨벤션은 type을 같이 넘겨서 어떤 종류의 행위인지 명시해주는 것
- dispath({type: 'age', age: 10}) 를 통해 dispatch 함수를 호출하는 경우
Return
Reducer는 바꾸고자하는 상태를 return 해야 한다.
Reducer를 생성할 때는 아래 두가지 사항을 주의해야한다.
- 첫번째 인자인 state는 readonly이므로 수정하지 말고 새로운 object를 return 할 것
- 여러 key를 가지는 object인 경우 변경하는 대상 외의 값들을 누락하지 않도록 주의할 것
- reducer는 pure 함수여야 한다. (비동기 함수 X)
✏️ useReducer 사용해보기
1. 상태 정의
리듀서가 동작하는 기본 상태를 정의해야 합니다.
예를 들어, 카운터 애플리케이션을 생각해보면, 상태는 카운터 값을 포함할 수 있습니다.
초기 상태를 initialState로 정의하고, 여기서는 count를 0으로 설정합니다.
const initialState = {
count: 0 // 초기 상태에서 카운터 값이 0
};
2. 리듀서(Reducer) 함수
리듀서 함수는 action의 type에 따라 상태를 업데이트합니다.
'INCREMENT'와 'DECREMENT'액션 타입을 사용하여 카운터 값을 조정합니다.
* type은 action 객체의 key 값으로 사용되는 문자열(액션 식별)
...state를 사용하는 이유 기존 상태를 유지하면서 필요한 부분만 업데이트하기 위함 (spread 연산자)
- INCREMENT : 카운터 값을 증가시킵니다. action.payload는 증가할 양을 나타냅니다.
- DECREMENT : 카운터 값을 감소시킵니다. action.payload는 감소할 양을 나타냅니다.
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + action.payload };
case 'DECREMENT':
return { ...state, count: state.count - action.payload };
default:
return state; // 기본 상태를 반환
}
}
3. dispatch 함수 호출
액션을 디스패치하여 상태를 업데이트할 수 있습니다.
// ▶ 상태와 액션 객체를 전달하여 dispatch 호출
// → 액션의 종류는 'INCREMENT'이고, 증가할 양은 1
// → 액션의 종류는 'DECREMENT'이고, 감소할 양은 1
dispatch({ type: 'INCREMENT', payload: 1 }); // 카운터를 1 증가시킵니다.
dispatch({ type: 'DECREMENT', payload: 1 }); // 카운터를 1 감소시킵니다.
📜 전체 코드
useReducer를 사용하여 상태와 액션을 관리하는 Counter 컴포넌트를 작성합니다.
버튼 클릭 시 dispatch를 호출하여 상태를 업데이트하고, 업데이트된 상태를 화면에 표시합니다.
import React, { useReducer } from 'react';
// ▶ 초기 상태 정의
const initialState = { count: 0 };
// ▶ 리듀서(Reducer) 함수 정의
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + action.payload };
case 'DECREMENT':
return { ...state, count: state.count - action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT', payload: 1 })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT', payload: 1 })}>Decrement</button>
</div>
);
}
export default Counter;
'📌 Front End > └ React' 카테고리의 다른 글
[React] 리액트 훅 - useEffect(조건부 실행, 자원 정리) (0) | 2024.08.30 |
---|---|
[React] 리액트 훅 - useContext(전역 상태값 공유) (0) | 2024.08.29 |
[React] 리액트 이벤트 처리 - 핸들러, 컴포넌트 상태(useState), 단방향 바인딩 (0) | 2024.08.26 |
[React] 리액트 내장 컴포넌트(Fragment, Profiler, StrictMode, Suspense, lazy) (0) | 2024.08.21 |
[React] 리액트 사용자 정의 함수형 컴포넌트(Functional Component) - 클래스와 객체 인스턴스를 사용해 데이터 관리 (0) | 2024.08.18 |