
오늘은 useReducer에 대해 학습해보았기 때문에, 그 기록을 남겨봅니다.
React에는 기본적으로 제공하는 내장 훅(Built-in Hooks)이 있는데,
useReducer는 React 내장 훅 중의 하나이다.
(Hook이 무엇인지에 대해 알고 싶다면 이 부분을 참고!)
- useState의 대체 함수입니다.
- 출처: Hooks API Reference - React 공식 문서
위와 같이 React 공식 문서에서 언급된 것처럼,
useReducer는 State(상태)를 관리하고 업데이트하는 Hook인 useState를 대체할 수 있는 Hook이다.
다시 말해, useReducer는 useState처럼 State를 관리하고 업데이트 할 수 있는 Hook이다.
여기서 잠깐✋
중요한 포인트.
useReducer의 묘미는,
⭐️한 컴포넌트 내에서 State를 업데이트하는 로직 부분을 그 컴포넌트로부터 분리시키는 것을 가능⭐️하게 해준다는 것이다.
그렇게 useReducer는 State 업데이트 로직을 분리하여
컴포넌트의 외부에 작성하는 것을 가능하게 함으로써, 코드의 최적화를 이루게 해준다.
🙄 ????
이게 무슨 뜻이냐?
React 튜토리얼에서 자주 등장하는 Counter 컴포넌트를
각각 useState, useReducer를 이용하여 만들어보았다.
아래의 두 코드를 비교해보자.
(코드 내용은 일단 스킵하고,
State 업데이트 로직이 어디에 위치했는지 큰 구조만 살펴 보자 🔍)
useState를 사용한 경우 : 컴포넌트 내부에 State 업데이트 로직이 존재

useReducer를 사용한 경우 : 컴포넌트 외부에 State 업데이트 로직이 존재

이쯤에서 다시 한 번 위의 설명을 읽어보자.
useReducer의 묘미는, ⭐️한 컴포넌트 내에서State를 업데이트하는 로직 부분을
그 컴포넌트로부터 분리시키는 것을 가능⭐️하게 해준다는 것이다.
'분리시키는 것을 가능하게 해준다'는 것은,
State 업데이트 로직을 또다른 파일에 작성해서 (분리), 분리된 파일을 불러와서 사용하는 것도 가능하다는 뜻도 된다.
이렇게 useReducer를 사용하면 컴포넌트의 최적화를 이룰 수 있다.
useReducer, useState 둘다 State를 변경하고 관리할 때 사용할 수 있다는 것을 알았다.
그럼 언제 useReducer를 사용하며, 언제 useState를 사용해야 할까?
정답은 없다고 하나, (본인이 조사해본 바) 여러 블로그에서 아래와 같이 소개하고 있는 것 같다.
useState
State가 1개일 경우State가 단순한 숫자, 문자열 또는 Boolean 값일 경우useReducer
State가 1개 이상, 복수일 경우State 값만 관리하지만, 추후 유동적일 가능성이 있는 경우State의 구조가 복잡해질 것으로 보이는 경우자, 위의 챕터를 읽었다면 useReducer를 언제 쓰는지, 왜 쓰는지에 대해서
대~충 감이 왔을거라고 생각이 든다.
이제 본격적으로 useReducer의 사용법과 구조에 대해 살펴보자.
React 튜토리얼에서 흔히 볼 수 있는 Counter 앱이다.
실제 Counter 앱의 움직임과 함께 코드를 보고 싶다면 여기로.
import React, { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "decrement":
// action의 type이 "decrement"일 때, 현재 state 객체의 count에서 1을 뺀 값을 반환함
return { count: state.count - 1 };
case "increment":
// action의 type이 "increment"일 때, 현재 state 객체의 count에서 1을 더한 값을 반환함
return { count: state.count + 1 };
default:
// 정의되지 않은 action type이 넘어왔을 때는 에러를 발생시킴
throw new Error("Unsupported action type:", action.type);
}
}
function Counter() {
const [number, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
{/* 현재 카운트 값은 state인 number 객체의 count로부터 읽어옴 */}
<h1>Count: {number.count}</h1>
{/* 카운트 값의 변경을 위해 각 버튼이 클릭되면 dispatch 함수가 발동되면서 reducer 함수가 실행됨.
dispatch 함수의 인자로, action 객체가 설정되었는데,
action 객체의 type에는 어떤 버튼을 클릭하였는지에 따라
"decrement" 또는 "increment"가 들어감
*/}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
}
export default Counter;
크게 4가지가 있다.
useReducer 함수는 첫번째 인자인 4️⃣ reducer 함수가 반환(return)하는 값으로 state를 갱신하는 역할을 한다.
기본적으로 useReducer는 다음과 같은 형태로 사용한다.
const [state, dispatch] = useReducer(reducer, initialState, init);
state: 컴포넌트에서 사용할 State(상태).dispatch: 4️⃣ reducer 함수를 실행시키며, 컴포넌트 내에서 state의 업데이트를 일으키기 위해서 사용하는 함수. 자세한 것은 아래 3️⃣ dispatch 함수 항목을 참고.reducer: 컴포넌트 외부에서 state를 업데이트하는 로직을 담당하는 함수. 현재의 state와 action 객체를 인자로 받아서, 기존의 state를 대체(replace)할 새로운 State를 반환(return)하는 함수. 자세한 것은 아래 4️⃣ reducer 함수 항목을 참고.initialState: 초기 Stateinit: 초기 함수전체 샘플 코드에서는 useReducer 함수를 다음과 같이 작성해주었다.
const [number, dispatch] = useReducer(reducer, 0);
action은 업데이트를 위한 정보를 가지고 있는 것이며, dispatch의 인자가 되며,
4️⃣ reducer 함수의 두번째 인자인 action에 할당된다.
action은 따로 정해진 형태는 없으나
아래의 코드와 같이 주로 type라는 값을 지닌 객체 형태로 사용된다고 한다.
우리의 샘플 코드를 예로 들면, { type: "decrement" } ← 이 부분이 action이다.
dispatch({ type: "decrement" })
dispatch 함수는 4️⃣ reducer 함수를 실행시킨다.
dispatch 함수의 인자로써 업데이트를 위한 정보를 가진 2️⃣ action를 이용하여,
컴포넌트 내에서 state의 업데이트를 일으키기 위해서 사용된다.
dispatch 함수의 인자인 2️⃣ action은 4️⃣ reducer 함수의 두번째 인자인 action에 할당된다.
function Counter() {
const [number, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
{/* 현재 카운트 값은 state인 number 객체의 count로부터 읽어옴 */}
<h1>Count: {number.count}</h1>
{/* 카운트 값의 변경을 위해 각 버튼이 클릭되면 dispatch 함수가 발동되면서 reducer 함수가 실행됨.
dispatch 함수의 인자로, action 객체가 설정되었는데,
action 객체의 type에는 어떤 버튼을 클릭하였는지에 따라
"decrement" 또는 "increment"가 들어감
*/}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
}
reducer 함수는 3️⃣ dispatch 함수에 의해 실행되며, 컴포넌트 외부에서 state를 업데이트하는 로직을 담당한다.
1️⃣ useReducer 함수의 첫번째 파라미터로 입력된 reducer 함수는,
현재의 state와 2️⃣ action을 인자로 받게 되는데,
이 2️⃣ action의 값에 근거하여 기존의 state를 대체(replace)할 새로운 state를 반환(return)한다.
아래 reducer 함수의 코드에서는, 간편하게 switch문을 이용하여
2️⃣ action의 값이 무엇인지에 따라 새로운 state를 반환(return)하고 있다.
(switch문을 사용하든, if문을 사용하든 방법은 자유.
이번에 샘플 코드에서 switch문을 사용한 이유는
if문을 사용하게 되면 2️⃣ action의 값에 맞춘 조건으로써 else if로 계속 처리를 만들어줘야하고,
연산자도 써줘야해서 번거롭기 때문.)
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);
}
}
reducer 함수에서 짚고 넘어가야할 점은,
기존의 state를 새로운 state로 대체(replace)한다는 것이다.
기존의 state를 변경(modify)하거나, 추가(add)하거나, 덮어쓰지(overwrite) 않는다는 것.
난 아직 Redux의 학습을 시작하지 않은 상태라 Redux에 대해 잘 알지는 못하지만,
Redux 또한 State management 기능을 수행하기 때문에
useReducer를 이해했다면 Redux의 기초를 이해하는 것에 도움이 된다고 한다.
(일석이조 🐶 이득❣️)
사실 이번 예시도 useReducer의 진가를 보여주기에는 부족한 예시였다고 생각하지만
간단한 예시를 통해 조금이라도 useReducer에 다가갈 수 있었기를 바란다.
조금 설명하기 어렵고 난해한(?) 부분이 없지 않아 있었던 것 같다.
그래서 꽤 장황하게 설명한 것 같지만...😭
이 글이 useReducer를 이해하고 싶은 누군가에게 유용한 자료가 되길 바라며...
잘못된 정보가 있으면 마구마구 지적해주시면 감사하겠습니다!
감사합니다 :)