Redux

gyu0714·2022년 11월 1일
0

react

목록 보기
2/2
post-thumbnail

3가지 원칙

하나의 근원

애플리케이션의 모든 상태는 하나의 저장소 안에 하나의 객체 트리 구조로 저장된다.

console.log(store.getState());

/* Prints
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
*/

상태는 읽기 전용이다

상태를 변화시키는 유일한 방법은 액션 객체를 전달하는 방법뿐이다.
이를 통해서 뷰나 네트워크 콜백에서 결코 상태를 직접 바꾸지 못 한다는 것을 보장할 수 있다. 모든 상태 변화는 중앙에서 관리되며 모든 액션은 엄격한 순서에 의해 하나하나 실행된다. 때문에, 신경써서 관리해야할 미묘한 경쟁 상태는 없다. 액션은 평범한 객체이며, 기록을 남길 수 있고, 시리얼라이즈할 수 있으며, 저장할 수 있고, 이후 테스트나 디버깅을 위해서 재현하는 것도 가능하다.

store.dispatch({
	type: 'COMPLETE_TOTO',
    index: 1
})

store.dispatch({
	type: 'SET_VISIBILITY_FILTER',
    filter: 'SHOW_COMPLETED'
})

변화는 순수 함수로 작성되어야한다.

액션에 의해 상태 트리가 어떻게 변화하는 지를 지정하기 위해 개발자는 순수 리듀서를 작성해야한다.
리듀서는 그저 이전 상태와 액션을 받아 다음 상태를 반환하는 순수 함수이다. 이전 상태를 변경하는 대신 새로운 상태 객체를 생성해서 반환해야 한다는 사실을 기억해야 한다. 처음에는 하나의 리듀서만으로 충분하지만, 애플리케이션이 성장해나가면 상태 트리의 특정한 부분들을 조작하는 더 작은 리듀서들로 나누는 것도 가능하다. 리듀서는 그저 함수이기 때문에 호출되는 순서를 정하거나 추가적인 데이터를 넘길 수도 있다. 심지어 페이지네이션과 같은 일반적인 재사용 가능한 리듀서를 작성하는 것도 가능하다.

// App.js
import { combineReducers, createStore } from 'redux'
const reducer = combineReducers({ visibilityFilter, todos })
const store = createStore(reducer)

function visibilityFilter(state = 'SHOW_ALL', action) {
	switch (action.type) {
    	case 'SET_VISIBILITY_FILTER':
      		return action.filter
    	default:
      		return state
    }
}

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}

상태(state)

type State = any

상태는 넓은 의미의 단어이지만, Redux API에서는 보통 저장소에 의해 관리되고 getState()에 의해 반환되는 하나의 상태값을 지칭한다. 상태는 Redux 애플리케이션의 전체 상태를 나타내며, 보통 깊게 중첩되어 있는 객체이다.

컨벤션에 따르면 최상위 상태는 객체나 Map과 같은 K-V 컬렉션이지만, 기술적으로는 어떤 타입이든 가능하다. 다만 상태를 항상 직렬화 가능하게 두는 것이 좋다. JSON으로 쉽게 변환될 수 없는 것들은 넣으면 안된다.

액션(Action)

type Action = Object

액션은 상태를 변화시키려는 의도를 표현하는 평범한 객체이다. 액션은 저장소에 데이터를 넣는 유일한 방법이다. UI 이벤트에서 왔든, 네트워크 콜백에서 왔든, 웹소켓과 같은 다른 소스에서 왔든 모든 데이터는 액션으로써 보내진다.

액션은 어떤 형태의 액션이 행해질지 표시하는 type필드를 가져야 한다. 타입은 상수로 정의되고 다른 모듈에서 임포트할 수 있다. 문자열은 직렬화될 수 있기 때문에 타입으로 Symbol 보다는 문자열을 쓰는 것이 좋다.

참고)

  • Flux Standard Action

  • 비동기 액션

    리듀서(Reducer)

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

    리듀서는 누적값과 값을 받아서 새로운 누적값을 반환하는 함수이다. 이들은 값들의 컬렉션을 받아서 하나의 값으로 줄이는데 사용된다.

    리듀서는 Redux만의 개념은 아니다. 기본 개념은 함수형 프로그래밍에서 왔으며, 자바스크립트와 같은 함수형이 아닌 프로그래밍 언어들에도 대부분 리듀싱을 위한 내장 API가 있다. 자바스크립트에서는 Array.prototype.reduce()가 해당된다.

    Redux에서 누적값은 상태 객체이고, 누적될 값은 액션이다. 리듀서는 주어진 이전 상태와 액션에서 새로운 상태를 계산한다. 이들은 반드시 같은 입력이 있으면 같은 출력을 반환하는 순수 함수여야만 한다. 이들은 부수효과를 가져서는 안된다.

    💡리듀서는 Redux에서 가장 중요한 개념이다💡

    💡API 호출을 리듀서 안에 넣으면 안된다💡

    디스패치 함수(Dispatching Function)

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

디스패치 함수는 액션이나 비동기 액션을 받는 함수이다. 받은 다음 하나나 여러개의 액션을 저장소에 보낼수도 보내지 않을수도 있다.
보통의 디스패치 함수와 저장소 인스턴스가 미들웨어를 거치지 않고 제공하는 기본 dispatch 함수를 구분해야 한다.
기본 디스패치 함수는 반드시 동기적으로 저장소의 리듀서에 액션을 보내야한다. 그러면 리듀서는 저장소가 반환한 이전 상태와 함께 새 상태를 계산한다. 리듀서가 사용하기 위해서 액션은 평범한 객체여야 한다.
미들웨어는 기본 디스패치 함수를 감싼다. 미들웨어를 통해 디스패치 함수는 액션 뿐 아니라 비동기 액션을 처리할 수 있다. 미들웨어는 액션이나 비동기 액션을 다음 미들웨어에 넘기기 전에 변환하거나, 지연시키거나, 무시하거나, 해석할 수 있다.

액션 생산자(Action Creator)

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

액션 생산자는 단지 액션을 만드는 함수이다. 액션은 정보의 묶음이고, 액션 생산자는 액션을 만드는 곳이다. 액션 생산자를 호출하면 액션을 만들어낼 뿐 디스패치하지 않는다. 저장소를 변경하기 위해서는 dispatch 함수를 호출해야 한다. 액션 생산자를 호출해 그 결과를 저장소 인스턴스로 바로 디스패치하는 함수를 바인드된 액션 생산자라고 부르기도 한다.
액션 생산자가 현재 상태를 읽어야 하거나, API 호출을 실행해야 하거나, 라우트 전환같은 부수효과를 일으켜야 한다면, 액션 대신 비동기 액션을 반환해야 한다.

비동기 액션(Async Action)

type AsyncAction = any

비동기 액션은 디스패치 함수로 보내지는 값이지만, 아직 리듀서에게 받아들여질 준비가 되어 있지는 않다. 비동기 액션은 기복 dispatch() 함수로 전달되기 전에 미들웨어를 통해 액션으로 바뀌어야 한다. 비동기 액션은 사용하는 미들웨어에 따라 서로 다른 타입이 될 수 있다. 종종 Promise나 Thunk와 같은 비동기 기본형으로, 리듀서에게 직접 전달되지는 않지만, 작업이 완료되면 액션을 보낸다.

미들웨어(Middleware)

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

미들웨어는 디스패치 함수를 결합해서 새 디스패치 함수를 반환하는 고차함수이다. 종종 비동기 액션을 액션으로 전환한다.
미들웨어 함수 결합을 통해 서로 결합할 수 있다. 이는 액션을 로깅하거나, 라우팅과 같은 부수효과를 일으키거나, 비동기 API 호출을 일련의 동기 액션으로 바꾸는데 유용하다.

참고)
applyMiddleware

저장소(Store)

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

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

  • dispatch(action)는 위에서 설명한 기본 함수이다.
  • getState()는 저장소의 현재 상태를 반환한다.
  • subscribe(listener)는 상태가 바뀔 때 호출될 함수를 등록한다.
  • replcaeReducer(nextReducer)는 핫 리로딩과 코드 분할을 구현할때 사용된다.
    참고) 저장소 API

저장소 생산자(Store Creator)

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

저장소 생산자는 Redux 저장소를 만드는 함수이다. 디스패치 함수와 마찬가지로, Redux 패키지에 들어있는 기본 저장소 생산자인 createStore(reducer, preloadedState)와 저장소 인핸서에서 반환되는 저장소 생산자는 구분해야한다.

저장소 인핸서(Store Enhancer)

type StoreEnhancer = (next: StoreCreator) => StoreCreator

저장소 인샌서는 저장소 생산자를 결합하여 강화된 새 저장소 생산자를 반환하는 고차함수이다. 이는 미들웨어와 비슷하게 조합가능한 방식으로 저장소 인터페이스를 바꿀 수 있게 해준다.

저장소 인핸서는 React에서 "컴포넌트 인핸서"로 불리는 고차 컴포넌트와 같은 개념이다.

저장소는 인스턴스라기보다는 함수들이 모인 보통의 객체이기 때문에, 기존의 저장소를 변경하지 않고도 복제본을 만들고 수정할 수 있다.
참고)

profile
gyu0714

0개의 댓글