오늘은 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
: 초기 State
init
: 초기 함수전체 샘플 코드에서는 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
를 이해하고 싶은 누군가에게 유용한 자료가 되길 바라며...
잘못된 정보가 있으면 마구마구 지적해주시면 감사하겠습니다!
많은 도움이 되었습니다 감사합니다