React의 useReducer 살펴보며 이해해보기

제이미·2024년 11월 22일
0

리액트

목록 보기
2/19
post-thumbnail

저번 React의 내장훅인 useRef에 이어 useReducer 이해하기 위해 쓰는 글

useReducer란?

useReducer is a React Hook that lets you add a reducer to your component.
출처: React 공식 문서 https://react.dev/reference/react/useReducer

공식 문서의 useReducer 정의에 따르면, 이는 컴포넌트에 "reducer"를 추가할 수 있게 해주는 리액트 훅이다. 라고 한다.

여기서 저 "reducer"가 뭘까..?

여기서 reducer란 상태를 업데이트하는 로직을 담고 있는 함수를 의미한다.

이 reducer는 2 개의 인자를 받아 새로운 상태를 반환하는 순수 함수라고 한다!
첫 번째 인자로는 현재 상태인 state, 두 번째로는 action으로 상태를 업데이트할 때 사용할 명령이나 정보를 담은 객체라 함
=> action으로 현재 state를 새로운 상태로 만들어 반환하는 듯

또 하나, reducer는 기존 상태를 직접 수정하지 않고 기존 상태를 기반으로 새로운 상태를 반환한다.

이 원칙이 불변성 즉, immutability를 유지해서 리액트의 상태 관리 효율성을 높여준다 함!

그래서 리액트 말로는 이러한 reducer를 컴포넌트에 추가할 수 있게 도와주는 것이 바로 useReducer라는 건데.. 계속 살펴보자

React에서는 useReducer가 이런 형태라고 한다

여기서 각각 파라미터가 의미하는 건 뭔지 알아보자
첫 번째 파라미터인 reducer
위에서 설명했듯이, 이는 상태를 업데이트 하는 순수 함수이며 반환값은 업데이트된 새로운 상태이다

두 번째 파라미터인 initialArg 아마도 inital argument의 준말같다.
이 파라미터의 타입은 any이며, 상태의 초기값을 의미한다
가장 기본적인 형태로 상태의 초기값을 직접 전달하는 부분이다

마지막 파라미터인 init. 이건 optional parameter이다.
이는 상태 초기화를 위한 함수이다.
initialArg를 인수로 받아 초기 상태를 계산한 후 반환한다고 하는데, init이 제공되지 않으면 initialArg가 바로 초기 상태로 사용된다고 한다!
-> 이 친구는 초기 상태를 비용이 많이 드는 연산을 통해 계산해야 할 때 유용하다고 한다

그 다음, [] 이 배열 안에 들어가는 state와 dispatch는 어떤 의미인지 살펴보자.
이 배열 속에 들어가는 두 가지는 반환값을 의미한다
첫 번째 state는 현재 상태, 두 번째 dispatch는 상태를 업데이트하기 위한 함수이며 액션 객체를 인수로 받는다 한다.

이 두 번째 말인 "dispatch는 상태를 업데이트하기 위한 함수이며 액션 객체를 인수로 받는다 한다"가 확 와닿게 이해가 되지 않는다. 다시 한번 살펴볼까?

dispatch는 말 그대로 파견, 발송 이런 의미의 단어이다.

이 dispatch는 useReducer에서 상태를 업데이트 하는 역할을 하는 함수이다.
그니까 어떤 동작(action)을 실행하라고 dispatch를 통해 useReducer의 첫 번째 인자인 reducer에게 전달한다.
위에서 말했지만, reducer는 2개의 인자를 받는다 state와 action.

그래서 다시 한번 dispatch를 이해해보자면
dispatch는 action 객체를 인자로 전달받아 reducer 함수로 전달을 한다는 것이다.

이 dispatch가 전달받는 액션 객체는 어떻게 생겼을까?

이 액션 객체는 보통 type 필드를 가지고 있고, 필요한 경우 payload도 포함한다고 한다.

여기서 또 payload가 뭘까..?

payload란 추가 데이터를 의미하는데, 정확히 얘기하자면 상태를 업데이트하는데 필요한 추가 정보를 의미한다고 한다.
매번 쓰이는 것은 아니고 필요할 때만 쓰이며, 예를 들어 "얼마를 더할지" 또는 "사용자 입력값" 같은 추가적인 데이터를 전달하는 필드라고 한다!

그래서 type과 payload를 포함한 액션 객체는 이런 식으로 생겼다

정리하자면, [] 이 배열 안에 들어있는 state는 reducer의 반환값인 새로운 상태를 받는 것이고, dispatch는 reducer에게 액션 객체를 전달하는 함수인 것이다.

그렇다면, 이 state와 dispatch를 전달받은 reudcer는 어떻게 로직을 구현할 수 있는지 살펴보자!

위의 예제 코드에서는 reducer 함수가 이제 다들 알고 싶듯이 2가지 인자를 받는다! state와 action!

그 다음 조건문으로 action의 type을 구분한다.
각 type에 대한 로직이 구현되어 있는데, 여기서는 type이 "increment"였을 때, return으로 새로운 상태를 반환한다.
여기서는 { count: state.count + action.value }이다.
그럼 { count: state.count + action.value } 이러한 형태의 새로운 상태로 반환하는 것이니 state가 이렇게 업데이트 되는 뜻이다.

이 dispatch에 액션 객체를 언제 전달하냐고?(요?)

원하는 이벤트 핸들러에서 dispatch 함수를 호출하면 된다

이렇게 원하는 이벤트 핸들러에 dispatch에 액션 객체를 전달해주면, state는 새로운 상태로 업데이트가 그때 되는 것이다!

최종적으로 정리를 해보자!

useReducer는 필수적으로 전달받아야 하는 인자는 2가지가 있는데, reducer 함수와 상태 초기값을 받는다.
useReducer의 반환값으로는 state와 dispatch가 있다.
컴포넌트 내에서 원하는 이벤트 핸들러에 dispatch를 호출해주는데 인자로 객체 형태인 액션 객체를 넣어준다.
이 액션 객체는 type 필드를 필수로 갖고 있고 필요하다면 추가적인 정보를 주는 payload 필드도 전달할 수도 있다!

그러면 반환값으로 받은 배열의 state는 처음 dispatch가 호출되어 reducer로 액션 객체를 전달하면, useReducer의 두 번째 인자인 초기 상태를 기반으로 새로운 상태가 된다.
그 후에 계속해서 이벤트가 발생한다면 그 이벤트 핸들러에서 호출되는 dispatch가 업데이트된 state를 기반으로 로직을 실행한다.

여기서 드는 생각은 useState로 상태를 정의해서 업데이트 시키는 것과 동일한 기능을 하는 거 같은데, 코드는 오히려 더 복잡하다고 생각이 든다.

그래서 useState와 useReducer의 차이점에 대해 한번 알아보도록 하자!

useState간단한 상태 관리에 적합하다!
상태 관리가 단순할 때 가독성과 유지보수성이 좋다는 장점을 갖고 있다.
setter 함수를 직접 호출함으로써 상태를 업데이트 한다.
하지만 상태가 복잡해질수록 관리가 힘들어진다는 단점!

useReducer복잡하거나 다차원적인 상태 관리에 적합하고 추가 로직이 필요해 상대적으로 복잡하다.
상태 업데이트를 액션 객체와 리듀서 함수로 진행한다.
상태가 여러 속성으로 구성되거나 상태 업데이트 로직이 복잡한 경우에 사용된다.
상태 변화에 관한 모든 로직이 reducer 함수에 모여 있기에 복잡한 상태 관리에 있어 가독성과 유지보수성이 좋다!
또한 상태 업데이트 로직을 함수로 분리하기때문에 테스트와 재사용이 쉽다는 장점을 지니고 있다 :)

복잡한 상태일 때가 언제일까..?

  • state(상태)가 여러 속성을 포함할 때
    -> 각 속성의 업데이트 로직이 다르지만 하나의 reducer 함수로 통합 관리가 가능하다!

  • form data처럼 다양한 입력 필드가 있고, 각 필드를 독립적으로 업데이트해야 할 때
    -> 아래의 initialState와 reducer 함수가 있으면
    -> 아래의 코드처럼 각각 필드를 업데이트 할 수 있다!

useReducer가 사용되는 때를 다시 정리해보자면
1. 상태가 복잡할 때

  • form, 게임 상태, 애니메이션 상태, 탭이나 모달 상태 등에서 주로 사용됨

2. 상태 변화 로직이 복잡할 때

  • 조건문에 따라 다르게 처리하거나 이전 상태를 참조해야 하는 경우
  1. 중앙 집중식 상태 관리가 필요할 때
  • 상태를 여러 컴포넌트에서 공유하고 상태 변경의 일관성 유지가 필요할 때
  1. 애플리케이션 규모가 커질 때
  • 프로젝트 규모가 커지고 여러 개발자가 함께 작업할 때, 상태를 일관되게 관라하고 디버깅 하기 쉬운 방식이 필요한 경우

useReducer는 컴포넌트 내에서만 필요한 상태를 관리할 때 적합하기에, 전역 상태 관리가 필요할 시 useContext와 결합하여 사용하거나 Redux, Zustand, Recoil, MobX 같은 상태 관리 라이브러리와 함께 사용을 하기도 한다!

profile
프론트엔드 개발하다 궁금할 때

0개의 댓글