일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 컴퓨터 구조
- 제어유니트
- 유튜브 클론코딩
- 깃허브 로그인
- 연결자료구조
- node.js
- 자바스크립트
- Express
- 표현식과 문
- 유튜브클론코딩
- 보조저장장치
- 마이크로명령어
- 제어유닛
- 컴퓨터 구조론
- 후위표기법변환
- 자료구조
- react
- mongoose
- cookie
- 리액트
- JavaScript
- Session
- Nodemon
- CPU
- pug
- 모던 자바스크립트 Deep Dive
- 프론트엔드
- 후위표기법연산
- 자바스크립트 배열
- MongoDB
- Today
- Total
909 Devlog
[React] Reducer 본문
👋 Reducer를 배우기 전에, state를 알고 계셔야 합니다
[React/개념] - [React] state, useState
제가 state에 대해 정리한 글이 있으니 한번 보고 오시는 걸 추천드립니다!
📌 Reducer란?
이전 글에서, 컴포넌트의 state를 useState를 사용해 관리했었습니다.
만약 우리가 To Do(할 일)✔️ 앱을 만든다고 생각 해 봅시다.
const [todos, setTodos] = useState([]);
useState를 사용해 ToDo 앱을 만들면, 할 일을 생성할 때, 수정할 때, 완료 표시를 할 때, 삭제할 때 등 여러 곳에서 설정자 함수인 setTodos를 불러와서 todos를 수정해야 할 것입니다.
새 기능을 추가하거나, 작성했던 코드를 수정하기 위해 코드 편집기의 여러 부분을 왔다 갔다 하면서 코드를 작성해야 하며, state가 업데이트되는 경우를 한눈에 파악하기 어려워 개발 효율도 안 좋아질뿐더러, 매우 어지러울 것 같아요 :(
이런 state를 업데이트 하는 로직들을 한 곳에 모아 관리할 수 없을까요? 🤔
네, 그럴 때 Reducer를 사용합니다.
Reducer는 state를 관리하는 한 방법입니다. useState를 사용했던 것처럼, useReducer도 비슷하게 사용할 수 있습니다.
👨💻 Reducer 사용 방법
여기 버튼을 누르면 숫자가 1 증가하거나 1 감소되는 리액트 코드가 있습니다.
import React, { useState } from "react";
function Counter() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber((prevNumber) => prevNumber + 1);
};
const onDecrease = () => {
setNumber((prevNumber) => prevNumber - 1);
};
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
코드 출처 : 벨로버트와 함께하는 모던 리액트
위의 예시코드는 코드가 짧아서 state가 어디서 어떻게 변하는지 한눈에 보이지만, 아까 말했듯이 코드가 길어지면 복잡해질 것입니다. 😅
useState를 통해 관리했던 state를 useReducer를 통해 관리해 보기 전에, Reducer의 기본 형태를 알아봅시다.
아래는 Reducer 함수의 기본 형태입니다.
function reducer(state, action) {
// 새로운 상태를 만드는 로직
// const nextState = ...
return nextState;
}
Reducer는 현재 상태(state)와 액션 객체(action)를 매개변수로 받아와서, 새로운 상태(nextState)를 반환하는 함수입니다.
Reducer에서 반환하는 상태는 곧 컴포넌트가 가지게 될 새로운 상태가 됩니다.
state는 우리가 사용하던 그 state이고,
action은 업데이트를 위한 정보를 가지고 있는 객체입니다.
// 숫자에 1을 증가시키기 위한 action 객체
{type: "INCREMENT"}
// 숫자에 1을 감소시키기 위한 action 객체
{type: "DECREMENT"}
// 예시 :
// 새 할 일을 등록하기 위한 action 객체
{
type: "ADD_TODO",
todo: {
id: 1,
text: "reducer 배우기",
done: false,
}
}
액션 객체는 type을 지정하고, 대문자와 _(언더바)로 구성하는 관습이 있지만 꼭 따라하지 않아도 됩니다.
type은 사용자가 어떤 행동을 했는지 판별하고, 그 행동에 맞는 함수를 찾고 작동시키기 위해 필요합니다.
이제 useReducer를 사용해 봅시다! 😁
const [state, dispatch] = useReducer(reducer, initialState);
state에는 우리가 useState에 사용하던 것처럼, 데이터의 이름을 지정합니다.
dispatch는 우리가 조금 전에 봤던 액션 객체, 그러니까 사용자의 액션을 reducer에 전달해 주는 함수입니다.
첫 번째 파라미터인 reducer는 Reducer 함수의 기본 형태에서 봤던 함수의 이름을 작성해 주시면 되고,
두 번째 파라미터인 initialState는 useState()의 괄호 안에 쓰던 초기값을 지정해 주시면 되겠습니다.
그럼 저는 이렇게 작성해야 겠군요
// Reducer 함수 작성
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// ----------- 구분 -----------
// useState를 useReducer로 교체
const [number, setNumber] = useState(0);
⬇️
const [number, dispatch] = useReducer(reducer, 0);
// ----------- 구분 -----------
// state를 업데이트 하는 함수들 교체
const onIncrease = () => {
setNumber((prevNumber) => prevNumber + 1);
};
const onDecrease = () => {
setNumber((prevNumber) => prevNumber - 1);
};
⬇️
const onIncrease = () => {
dispatch({type: "INCREMENT"});
};
const onDecrease = () => {
dispatch({type: "DECREMENT"});
};
이제 좀 감이 오시나요?
유저가 만약 숫자를 증가시키는 버튼을 눌렀다면
onIncrease 함수가 작동하고, 그 함수 내부의 dispatch가 작동해서 사용자의 액션을 Reducer 함수에 전달하게 됩니다.
Reducer 함수 내부에서 switch case 문을 통해 state(number)를 알맞게 변화시켜 준 뒤 컴포넌트가 다시 렌더링 됩니다.
최종 코드입니다.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
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;
코드 출처 : 벨로버트와 함께하는 모던 리액트
state 관리를 더 쉽고 편하게 하기 위해서 reducer를 사용했었죠?
그래서 reducer 함수를 아예 다른 파일로 분리하는 것도 가능합니다.
그럼 reducer 파일에는 export default 만 추가한 reducer 함수만 존재하는 거죠!
export default function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
이제 파일마다 관심사를 분리했으니 컴포넌트 로직을 더 쉽게 읽고 이해하실 수 있을 겁니다 :)
이제 이벤트 핸들러(on"..." 함수)에는 action만 전달하고
Reducer 파일로 와서 해당 action에 대한 return을 잘 작성하기만 하면 되는 거죠 😁
📌 Reducer도 좋은 점만 있는 것은 아닙니다.
좋은 점 😀
🎯 디버깅
useState에 버그가 있는 경우, state가 어디서 잘못 설정되었는지 알기 어려울 수 있습니다.
useReducer를 사용하면 reducer 함수에 console.log()를 찍어보며 어떤 action에서 버그가 발생했는지 비교적 쉽게 확인할 수 있습니다.
🎯 테스팅
reducer는 컴포넌트에 의존하지 않는 순수한 함수로써, 별도로 분리해서 테스트할 수 있습니다.
나쁜 점 😅
🎯 작성해야 할 코드가 늘어납니다.
일반적으로 useState를 사용하면 작성해야 할 코드가 줄어들지만
useReducer를 사용하면 reducer 함수와 action을 전달하는 부분을 모두 작성해야 해서 코드가 길어집니다.
🎯 간단한 로직일 때는 useState가 가독성이 더 좋습니다.
useState로 간단하게 state를 업데이트하는 경우가 reducer를 사용하는 것에 비해 코드 가독성이 좋습니다.
하지만 state 로직이 복잡해지면 reducer를 사용해 분리해서 깔끔하게 볼 수 있습니다.
📌 그럼 어쩔 때 useReducer를 사용할까요?
이 부분에 정해진 답은 없습니다.
위에서 좋은 점과 나쁜 점을 봤듯이 편할 때도 있고, 불편할 때도 있습니다.
컴포넌트에서 관리하는 state가 간단한 값 (숫자, 문자열, boolean) 하나라면 useState가 편해 보입니다.
하지만 2중 배열이라던가 배열 안에 객체가 있는 state들을 관리해야 한다면 useReducer가 좋은 선택이 될 수도 있겠지요.
둘 다 사용해 보시고, 좀 더 자신에게 맞고, 현재 코드에 맞는 편한 방법을 찾아봅시다! 💪
'React > 개념' 카테고리의 다른 글
[Redux] subscribe (0) | 2023.09.07 |
---|---|
[React] state, useState (1) | 2023.09.03 |