Redux와 Reducer

LazyMG·2025년 9월 7일
0

개발 일기

목록 보기
4/4
post-thumbnail

최근 상태 관리 라이브러리 기술 폭을 넓히고자 마음을 먹었습니다. 리코일만 많이 사용했기 때문에 다양한 라이브러리를 배우고 싶더라구요. 그래서 제일 먼저 선택한 라이브러리는 바로 리덕스(Redux)였습니다! 리코일을 배우면서 리덕스에 관해 들어보긴 했지만 직접 사용해보는 것은 처음이라 두근 거리는 마음으로 공부를 시작했습니다. 확실히 리코일에 비해 개념적으로 알아야 할 것들이 많아 초기에 감을 잡는 데에 시간을 많이 걸렸습니다.

리덕스를 공부하면서 인상 깊었던 개념에는 Flux 패턴, Reducer 등이 있었는데요, 이번에 리듀서(Reducer)에 대해 정리해보려고 합니다. 자바스크립트의 배열 메소드 중에 하나인 reduce와 유사한 점이 있는지도 알아보려고 합니다. 리덕스의 리듀서 함수에 대해 정리할 때는 react-redux 라이브러리를 사용하려고 합니다.

리듀서

리듀서(reducer)는 한국어로 '감속기'라는 뜻을 갖고 있습니다. 프로그래밍에서는 여러 입력을 줄여 하나의 결과를 만드는 역할을 하곤 하는데요, 이전 값과 현재 입력을 받아 새로운 값을 돌려주는 순수 함수로 사용됩니다.

리듀서는 순수 함수로 사용되기 때문에 리듀서 함수 안에서는 부수 효과(side-effect)를 발생시키는 것이 금지되며 같은 입력일 때 항상 같은 출력을 기대합니다. 또한 리듀서를 통해 기존 값을 변경(mutate)하지 않아야 하고 초깃값을 분명하게 명시해야 합니다.

자바스크립트 배열의 reduce 메소드

자바스크립트의 배열에는 reduce 메소드가 존재합니다. reduce 메소드는 callback 함수인 리듀서 함수초기값을 매개변수로 갖습니다.

arr.reduce(callback[, initialValue]);

리듀서 함수

리듀서 함수(callback 함수)는 네 개의 인자를 가집니다.

1) 누산기(acc)* → 누산기는 콜백의 반환값을 누적합니다. 초기값이 주어진 경우, 초기값으로 설정됩니다.
2) 현재 값(cur)* → 처리할 현재 요소입니다.
3) 현재 인덱스(idx) → 처리할 현재 요소의 인덱스입니다. 초기값이 주어진 경우 0, 아니면 1부터 시작합니다.
4) 원본 배열(src) → 현재 배열입니다.

초기값

초기값(initialValue)은 강제되지 않습니다. 하지만, 리듀서 함수의 정의에 따르면 초기값이 분명히 명시되어야 하고 빈 배열에서의 reduce 메서드 사용은 오류를 발생시킬 수 있기 때문에 초기값을 명시하는 것이 권장됩니다.

reduce 메소드의 대표적인 예제는 숫자 배열의 모든 요소를 더한 값을 구하는 예제입니다.

// 숫자 배열
const numArr = [1, 2, 3, 4, 5];

const totalNum = numArr.reduce((acc, cur) => acc + cur, 0); // 15

아래와 같은 유사한 예시를 만들어볼 수 있습니다.

const totalNumToStr = numArr.reduce((acc, cur) => acc + cur.toString(), ''); // 12345

const totalIdx = numArr.reduce((acc, cur, idx) => acc + idx, 0); // 10

리덕스의 리듀서

리덕스 라이브러리에서 리듀서 함수는 스토어가 디스패치된 액션을 받으면 현재 상태와 함께 호출되어 액션에 따른 ‘다음 상태’를 계산해 반환하는 함수입니다.

기존 리듀서 함수의 정의에서, 값을 상태로, 입력은 액션으로 변경해서 이해할 수 있습니다. 즉, 리덕스에서 리듀서 함수는 이전 상태와 현재 액션을 받아 새로운 상태를 돌려주는 순수 함수입니다.

상태 관리를 위해 store를 생성하기 위해서는 리듀서 함수와 초기값이 반드시 필요합니다.

import { createStore } from "redux";

const initialValue = {
	count: 0
}; // 초기값

const reducer = () => {}; // 리듀서 함수

const store = createStore(reducer, initialValue); // 스토어

스토어를 생성하는 데 사용되는 리듀서 함수에 대해 좀 더 자세히 알아보겠습니다. 이 리듀서 함수에는 이전 상태(값)와 액션(입력)이 인자로 들어옵니다. 액션에 따라 새로운 상태를 만들고 이를 반환하는 방식으로 자주 사용됩니다.

const reducer = (state, action) => {
	switch(action.type) {
      case 'increment':
        return { count: state.count + 1 };
      case 'decrement':
        return { count: state.count - 1 };
      default:
        return state;
    }
};

전통적인 리덕스를 사용할 때는 주의할 점이 있습니다. 바로 불변성입니다. 이전 상태를 변경하여 반환하는 것이 아니라 새로운 상태를 만들어 반환해야 합니다.

const reducer = (state, action) => {
	switch(action.type) {
      case 'increment':
        state.count++;  // ❌ 이전 상태 직접 변경
        return state;	// ❌ 이전 상태 반환
      case 'decrement': // ❌ 이전 상태 직접 변경
        state.count--;	// ❌ 이전 상태 반환
        return state;
      default:
        return state;
    }
};

또한 리듀서 함수는 순수 함수이기 때문에 상태를 업데이트하는 동작 이외에는 수행해서는 안됩니다. 특히 리듀서 함수 내에서 http 요청(비동기 요청)을 보낼 수 없습니다.

리덕스의 리듀서 함수는 초기값으로 주어진 상태의 타입을 유지하면서 반환해야 합니다. 리듀서 함수를 비동기 함수로 바꾸어고 반환되는 비동기 리턴(Promise)는 리덕스가 요구하는 상태가 아니기 때문에 에러를 발생하게 됩니다.

const reducer = async (state, action) => {}; // ❌ 리덕스 에러 

리듀서 함수를 비동기 함수가 아닌 일반 함수로 하고 비동기 요청을 보내게 되면, 요청의 결과를 기다리지 않고 이전 상태를 그대로 반환하게 됩니다.

// 1. return 안하기
switch(action.type) {
  case 'increment':
    return { count: state.count + 1 };
  case 'decrement':
    return { count: state.count - 1 };
  case 'get':
    fetch('/api/count')
      .then(res => res.json())
      .then(data => ({ count: data }))  // 기다리지 않음
      .catch(() => state); // -> switch문에서 return이 없어서 default로 넘어감
  default:
    return state;
}

// 2. return 명시
switch(action.type) {
  case 'increment':
    return { count: state.count + 1 };
  case 'decrement':
    return { count: state.count - 1 };
  case 'get':
    await fetch('/api/count')
      .then(res => res.json())
      .then(data => ({ count: data }))
      .catch(() => state);
    return state;  // 비동기 요청의 결과를 기다리지 않음
  default:
    return state;
}

지금까지 자바스크립트와 리덕스 라이브러리에서 사용하는 리듀서 함수의 개념에 대해 정리해봤습니다. 리듀서 함수의 개념이 다양하게 사용되는 경우를 보면서 프로그래밍 개념 하나를 확실하게 알고 있으면 적용이나 로직의 이해에 도움이 된다는 것을 다시 한 번 알 수 있었습니다.

참고

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

profile
개발 기록

0개의 댓글