Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
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 31
Archives
Today
Total
관리 메뉴

나는개발자니까

[Redux 공식문서] 프로젝트 유지보수를 위한 Redux 스터디 본문

Redux

[Redux 공식문서] 프로젝트 유지보수를 위한 Redux 스터디

된다고했잖아요 2024. 11. 18. 23:37

 

 

이번 회사 프로젝트에서 상태관리를 Redux로 하고 있어 공식문서를 번역하면서 중요해 보이는 부분만 작성해보았다.

Redux 왜 생겨났을까요?

프론트엔드 개발이 복잡하고 어려운 이유는 무엇일까요? 단순한 웹 애플리케이션을 벗어나 점점 복잡한 기능이 늘어나고 있는 요즘, 웹 애플리케이션을 만들기 위해 많은 상태를 관리해야 합니다. 기존 프로젝트에서 계획에 없던 상태를 하나씩 추가해 나가 여러 상태를 관리하게 되면 어느 순간부터는 개발자 조차 상태가 언제, 왜, 어떻게 업데이트하는지 디버깅하기 어려운 시점에 도달하게 되며 버그를 재현하기 어렵습니다.

이보다 근본적인 이유는 사람이 추론하기 어려운 끊임없는 변화(mutaion)와 비동기(asyncronicity)의 개념을 섞어서 사용하기 때문입니다. 이 문제를 해결하기 위해 Redux는 상태 변화가 일어나는 시점에 *Flux, *CQRS, *Event Sourcing에 따른 제약을 두어 상태 변화를 예측 가능하게 만듭니다.

 

* Flux : Facebook이 만든 클라이언트 사이드 웹 애플리케이션 아키텍처 패턴입니다. 단방향 데이터 흐름을 가지고 있습니다. (Action → Dispatcher → Store → View)

* CQRS : 명령(Command)과 조회(Query)를 분리하는 패턴입니다.

* Event Sourcing : 상태 변화를 이벤트의 순서로 저장하는 패턴입니다.

 

Redux의 기본 핵심 용어 정리

상태

type State = any

상태는 getState()에 의해 반환되는 하나의 상태값입니다. Redux 애플리케이션의 전체 상태이며 깊게 중첩되어 있는 객체입니다.

 

액션

type Action = Object

액션은 상태를 변화시키려는 의도를 표현하는 객체입니다. 모든 데이터는 액션으로 인해 저장소에 데이터를 변화시킵니다.

 

리듀서

type Reducer<S, A> = (state: S, action: A) => S

리듀서는 상태 객체를 받아서 액션을 반환하는 함수입니다. 주어진 이전 상태와 액션에서의 새로운 상태를 계산하여 반환합니다. 리듀서에서 주의할 점은 API 호출 로직을 넣지 않는 것입니다.

 

디스패치 함수

type BaseDispatch = (a: Action) => Action
type Dispatch = (a: Action | AsyncAction) => any

 

액션 생산자

type ActionCreator<A, P extends any[] = any[]> = (...args: P) => Action | AsyncAction

액션 생산자는 액선을 만드는 함수입니다. 만약 액션 생산자가 상태를 읽거나, API 호출을 실행해야 하거나, Route 전환 등과 같은 Side Effect가 필요하다면 비동기 액션을 반환해야 합니다.

 

비동기 액션

type AsyncAction = any

비동기 액션은 dispatch() 함수로 보내지는 값이지만, 아직 리듀서에게 받들여질 준비가 되어 있지 않습니다. 비동기 액션은 기본 dispatch() 함수로 전달되기 전에 미들웨어를 통해 액션으로 변경되어야 합니다.

 

미들웨어

type MiddlewareAPI = { dispatch: Dispatch, getState: () => State }
type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch

미들웨어는 데스패치 함수를 결합하여 새 디스패츠 함수를 반환하는 고차함수입니다. 액션을 실행하거나, Routing과 같은 Side Effect를 일으키거나 비동기 API 호출을 동기 액션으로 전환할때 사용합니다.

 

저장소

type Store = {
  dispatch: Dispatch,
  getState: () => State,
  subscribe: (listener: () => void) => () => void,
  replaceReducer: (reducer: Reducer) => void
}

저장소는 애플리케이션의 상태 트리를 가지고 있는 객체입니다. 리듀서에서 결합이 일어나기 때문에, Redux 앱에는 단 하나의 저장소만 있어야 합니다.

 

저장소 생산자

type StoreCreator = (reducer: Reducer, preloadedState: ?State) => Store

저장소 생산자는 Redux 저장소를 만드는 함수입니다.

 

저장소 인핸서

type StoreEnhancer = (next: StoreCreator) => StoreCreator

저장소 인핸서는 저장소 생산자를 결합하여 강화된 새 저장소 생산자를 반환하는 고차함수입니다.

 

  • 인핸서 (Enhancer) : 컴포넌트를 감싸서 새로운 기능을 추가하는 고차 컴포넌트(HOC)의 한 형태입니다. 기존 컴포넌트의 로직을 재사용하고 확장하는 방식을 말합니다.

 

Redux는 어떤것일까요?

Redux 애플리케이션의 중심은 바로 store입니다. store는 애플리케이션의 global 전역 상태 객체를 저장하는 컨테이너 입니다. store는 순수 global 객체보다 특별한 가능이 추가된 JavaScript 객체입니다.

 

Redux의 기본 동작

  1. Redux Store에 있는 상태는 직접적으로 수정하거나 변경할 수 없습니다.
  2. 상태를 업데이트 할 수 있는 유일한 방법은 action 객체에 애플리케이션에서 어떤 상태를 업데이트 할 것인지 정의하고 action을 dispatch해야 store에 업데이트 사항을 전달할 수 있습니다.
  3. 만약 action이 dispatch되었을 경우 store는 root reducer 함수를 실행시키고 이전의 상태와 action을 기반으로 새로운 상태를 계산합니다.
  4. 최종적으로 store는 상태가 업데이트 되어 UI가 새로운 데이터와 함께 업데이트 되었을 subscribers에 알립니다.

 

State, Actions, and Reducers

Action

action은 type 필드를 가지고 있는 순수 JavaScript 객체이며 애플리케이션에서 발생한 일을 설명합니다. type 필드는 action에 대한 설명이 들어간 이름을 작성합니다. 보통 “domain/eventName’ 와 같이 작성하며 domain은 action에 관련된 형태나 카테고리를 작성하고 eventName은 발생하는 event에 대한 내용을 작성한다. 이외에도 action을 정확하게 구분하기 위한 필드가 필요하다면 추가로 작성할 수도 있다.

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

 

Reducers

reducer는 조건에 따라 어떻게 상태를 업데이트할 지를 결정하는 최신 상태와 action객체를 받는 함수입니다. 따라서 reducer는 action 타입애 따라 event를 처리하는 event listener라고 생각할 수 있습니다.

 

Reducers는 반드시 아래의 규칙을 따라야 합니다.

  1. 이전의 상태와 action을 기반으로 새로운 상태를 계산하는 것만 수행하도록 한다.
  2. 존재하는 상태값을 수정하지 않는다. 대신, 기존 상태를 복사하고 복사된 값에 변경 사항을 적용하여 ‘불변성’을 유지한 상태로 업데이트해야 한다.
  3. 난수값을 계산하거나 side effect를 발생시키는 비동기적인 로직을 가지지 않는다.

 

Reducers 로직

  • actions에 대해 reducer가 처리해야할 내용이 있다면 상태를 복사하고 필요 시 새로운 값을 업데이트하여 반환하고 변경되지 않을 경우 기존 상태를 반환합니다.
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/incremented') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}

 

Store

Redux 애플리케이션의 최신 상태는 store에서 가져오는 객체를 저장하고 있습니다. store는 reducer를 거쳐 생성되며 최신 상태값을 반환하는 getState를 호출합니다.

import { createStore } from 'redux'
import rootReducer from './reducer'

const store = createStore(rootReducer)

export default store

 

* Redux 애플리케이션의 store는 1개이기 때문에 만약 data handling 로직을 분리하고 싶다면 reducer composition를 사용해 multiple reducer를 만들어 분리된 store 대신 사용할 수 있습니다.

 

Dispatch

Redux store는 dipatch 메서드를 가지고 있습니다. store.dispatch 메서드를 호출하고 action 객체를 거치는 것은 상태를 업데이틀 하기 위한 유일한 방법입니다. store는 reducer 함수가 실행되고 새로운 상태값을 저장하며 업데이트된 상태를 반환하기 위해 getState 메서드를 호출할 수 있습니다. action을 Dispatch하는것은 애플리케이션에서 event를 trigger하는 것이라고 생각할 수 있습니다.

store.dispatch({ type: 'counter/incremented' })

console.log(store.getState())
// {value: 1}

 

Selectors

Selector는 store 상태값에서 특정 정보를 추출할 수 있는 함수입니다. 애플리케이션의 규모가 더 커질 경우 다른 패아지에서 같은 데이터가 필요한 경우 로직이 반복되는 현상을 피할 수 있습니다.

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

 

Redux Core Concepts and Principles

1. 단일 소스의 원칙

웹 애플리케이션의 모든 상태는 하나의 저장소 안에 하나의 객체 트리 구조로 저장됩니다. 웹 애플리케이션의 global 객체는 단일 store의 객체로 저장되어 있습니다. 특정 데이터 정보는 여러 곳에 중복되어 존재하는 것이 아니라, 단 하나의 위치에만 존재해야 합니다.

이 원칙은 웹 애플리케이션의 상태를 디버킹하고 검사하기 쉬워지며 전체 웹 애플리케이션와 상호작용하는 로직을 중앙화할 수 있습니다.

2. 상태는 읽기 전용이다.

상태를 변경시키는 유일한 방법은 action을 dispatch하는 것입니다. UI는 데이터를 중복하지 않고 상태가 업데이트한 원인을 추적할 수 있습니다. 즉, 우리는 뷰나 네트워크 콜백에서 절대 상태를 직접 변경할 수 없음을 보장할 수 있습니다.

모든 상태의 변화는 중앙에서 관리되며 모든 액션은 엄격한 순서에 의해 하나하나 실행되기 때문에 디버깅이나 테스트 목적을 위해 상태 변화를 예측 가능하고 추적할 수 있습니다.

3. 순수 Reducer 함수를 통해 상태를 변화시켜야 한다.

액션에 의해 상태 트리가 어떻게 변화하는지 정의하기 위해 순수 Reducer를 작성해야합니다.

Reducer의 역할은 이전 상태와 액션을 받아 다음 상태를 반환하는 순수 함수입니다. 이전 상태를 변경해주며 대신 새로운 상태 객체를 생성하여 반환합니다. 또한 상태 트리의 특정한 부분들을 조작하는 더 작은 리듀서들로 나눌 수 있습니다.

 

Redux 스터디를 마무리하며

아직은 Redux 공식문서에 보이는 것들이 이정도이지만 추후 프로젝트 유지보수를 하면서 더 알게 되는 부분이 있다면 추가로 블로그에 공유할 예정이다.