Introduction
많은 상태를 관리할 필요가 생겨났다.
상태
서버 응답, 캐시 데이터, 지역적으로 생성해서 사용하고 있지만 아직 서버에 저장되지 않은 데이터,
활성화된 라우트, 선택된 탭, 로딩을 보여줄지 여부, 페이지네이션 컨트롤 등 다양한 UI 상태
모델이 다른 모델을 업데이트하고, 그리고 뷰가 모델을 업데이트 할 수 있고, 이 뷰가 다시 다른 모델을 업데이트하고, 이에 따라 또 다른 뷰가 업데이트 된다.
더하여, 프론트엔트 제품 개발에 있어서 새로 갖춰야할 복잡한 요건들이 늘어나고 있다. 낙관적 업데이트(Optimistic update), 서버 렌더링, 라우트가 일어나기 전에 데이터 가져오기 등이 이에 해당한다. 이러한 복잡함은 변화(mutation)
나 비동기(asyncronicity)
와 같이 사람이 연동하여 추론하기 어려운 개념을 섞어서 사용한다 는 데서 비롯된다.
비관적(pessimistic) 업데이트
사용자 입력 -> 수정 요청 -> 성공 시 화면 갱신
- 사용자에게는 불편하지만, 개발자에게는 쉬움
낙관적(optimistic) 업데이트
사용자 입력 -> 바로 화면 먼저 갱신 -> 수정 요청
- e.g. slack, trello
Redux
자바스크립트 앱을 위한 예측 가능한 상태 컨테이너
일관적, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동, 테스트하기 쉬운 앱 작성을 도와준다.
1 | npx create-react-app my-app --template redux |
애플리케이션의 모든 상태는 하나의 저장소 안에 하나의 객체 트리 구조로 저장
- 범용적인 애플리케이션(universal application, 하나의 코드 베이스로 다양한 환경에서 실행 가능한 코드)을 만들기 쉽다.
- 서버로부터 가져온
상태
는 연결되거나(serialized) 수화되어(hydrated) 전달되며 클라이언트에서 추가적인 코딩 없이도 사용할 수 있다.
상태를 변화시키는 유일한 방법은 무슨 일이 벌어지는 지를 묘사하는 액션 객체를 전달하는 방법뿐
- 뷰나 네트워크 콜백 등에서 상태를 직접 바꾸지 못 하도록 보장한다.
변화는 순수 함수로 작성되어야
reducer
는이전 상태
와액션
을 받아 다음 상태를 반환하는 순수 함수이다. 이 때, 이전 상태를 변경하는 대신 새로운 상태 객체를 생성해서 반환해야한다.- 처음에는 하나의 reducer 만으로 충분하지만, 애플리케이션이 성장해나가면 상태 트리의 특정한 부분들을 조작하는 더 작은 개별적인 리듀서들로 나누는 것도 가능하다. (React 의 경우 하나의 루트 컴포넌트에서 시작해서 여러 작은 컴포넌트의 조합으로 나누는 것과 동일)
Concepts
상태
1 | type State = any |
상태(상태 트리)는 넓은 의미의 단어이지만, Redux API에서는 보통 저장소에 의해 관리되고
getState()
에 의해 반환되는 하나의 상태값 을 지칭한다.(넓은 의미의) 상태는 Redux 애플리케이션의 전체 상태 를 나타내며, 보통 깊게 중첩되어 있는 객체입니다.
액션
1 | type Action = Object |
- 액션은 상태를 변화시키려는 객체이다.
- 어떤 형태의 액션이 행해질지 표시하는
type
필드를 가져야 한다. type은 상수로 정의되고 다른 모듈에서 임포트할 수 있다.
리듀서
1 | type Reducer<S, A> = (state: S, action: A) => S |
리듀서(리듀싱 함수)는
누적(state)값
과(누적될) 값
을 받아서 새로운 누적값을 반환 하는 함수이다. 이들은 값들의 컬렉션을 받아서 하나의 값으로 줄이는데 사용된다.Redux에서 누적”값”은 상태 객체이고, 누적”될” 값은 액션이다. 리듀서는 주어진 이전 상태와 액션에서 새로운 상태를 계산한다.
반드시 같은 입력이 있으면 같은 출력을 반환 하는 순수 함수여야 한다.

디스패치 함수
1 | // 기본 디스패치 함수 |
디스패치 함수는 액션이나 비동기 액션을 받는 함수이다.
저장소 인스턴스가 미들웨어를 거치지 않고 제공하는 기본
dispatch
함수- 반드시 동기적으로 저장소의 리듀서에 액션을 보내야 한다. 그러면 리듀서는 저장소가 반환한 이전 상태와 함께 새 상태를 계산한다.
- 👾 리듀서를 사용하기 위해서 액션은 평범한 객체여야 한다.
비동기 dispatch 함수 (보통 dispatch 함수)
- 미들웨어를 통해 디스패치 함수는 비동기 액션을 처리할 수도 있다. 미들웨어가 기본 디스패치 함수를 감쌈으로써 액션이나 비동기 액션을 다음 미들웨어에 넘기기 전에, 변환하거나, 지연시키거나, 무시하거나, 해석할 수 있다.
액션 생산자
1 | type ActionCreator<A, P extends any[] = any[]> = (...args: P) => Action | AsyncAction |
액션 생산자는 단지 액션을 만드는 함수이다. 이 때, 액션은 정보의 묶음이고, 액션 생산자는 액션을 만드는 곳이다.
액션 생산자를 호출하면 액션을 만들어낼 뿐 dispatch하지는 않는다. 따라서 저장소를 변경하기 위해서는
dispatch
함수를 호출해야 한다.- 액션 생산자를 호출해 그 결과를 저장소 인스턴스로 바로 dispatch하는 함수를 바인드된 액션 생산자라고 부르기도 한다.
- 비동기 액션은 디스패치 함수로 보내지는 값이지만 아직 reducer에게 받아들여질 준비가 되어 있지 않아, 기본
dispatch()
함수로 전달되기 전에 미들웨어를 통해 액션(이나 일련의 액션들)으로 바뀌어야 한다.- 이들은 종종
Promise
나Thunk
와 같은 비동기 기본형으로, 리듀서에게 직접 전달되지는 않지만, 작업이 완료되면 액션을 보낸다.
- 이들은 종종
미들웨어
1 | type MiddlewareAPI = { dispatch: Dispatch, getState: () => State } |
중간 (단계) 역할자
- 미들웨어는 dispatch 함수를 결합해서 새 dispatch 함수를 반환하는 고차함수이다.
- 종종 비동기 액션을 액션으로 전환한다.
저장소
1 | type Store = { |
- 저장소는 애플리케이션의 상태 트리를 가지고 있는 객체이다. reducer 수준 (상태 관리) 에서 결합이 일어나기 때문에, Redux 앱에는 단 하나의 저장소만 있어야 한다.
dispatch(action)
: 기본 디스패치 함수getState()
: 저장소의 현재 상태를 반환subscribe(listener)
: 상태가 바뀔 때 호출될 함수를 등록replaceReducer(nextReducer)
: 핫 리로딩과 코드 분할을 구현할때 사용
저장소 생산자
1 | type StoreCreator = (reducer: Reducer, preloadedState: ?State) => Store |
- Redux 저장소를 만드는 함수
저장소 인핸서
1 | type StoreEnhancer = (next: StoreCreator) => StoreCreator |
- 저장소 생산자를 결합하여 강화된 새 저장소 생산자를 반환하는 고차함수
- 미들웨어와 비슷하게 조합가능 한 방식으로 저장소 인터페이스를 바꿀 수 있도록 한다.
Do I really need?
Redux를 사용하기 적절한 때는,
- 계속해서 바뀌는 상당한 양의 데이터가 있다
- 상태를 위한 단 하나의 근원 (루트 저장소) 이 필요하다
- 최상위 컴포넌트가
모든 상태를 가지고 있는 것은 더 이상 적절하지 않다.
Basic Example
앱의 상태 전부는 하나의 저장소(store)안에 있는 객체 트리에 저장된다.
상태 트리를 변경하는 유일한 방법은 무엇이 일어날지 서술하는 객체인 액션(action)을 보내는 것 뿐이며,
액션이 상태 트리를 어떻게 변경할지 명시하기 위해 리듀서(reducer)를 작성해야 한다.
1 | import { createStore } from 'redux' |
상태를 바로 변경하는 대신, 액션이라 불리는 평범한 객체를 통해 일어날 변경을 명시한다.
그리고 각각의 액션이 전체 애플리케이션의 상태를 어떻게 변경할지 결정하는 특별한 함수인 리듀서를 작성합니다.
- 보통의 Redux 앱에는 하나의 루트 리듀서 함수를 가진 단 하나의 저장소가 있다.
Sample
action은 setter
가 없는 모델(클래스)과 같은 객체이다. 다른 코드가 임의로 이를 수정할 수 없기 때문에, 버그 발생률을 낮춘다.
1 | { |
state을 바꾸기 위해서는 action을 dispatch 해야 한다. action은 (플레인) 자바스크립트 객체로 전달한다.
1 | { type: 'ADD_TODO', text: 'Go to swimming pool' } |
이렇게 모든 변화를 action으로 표현하는 것은 앱의 세부적인 변화부터 전체적인 변화까지 파악할 수 있도록 한다.
이러한 state과 action을 묶기 위해서, reducer를 사용한다. 다만, reducer은 큰 규모로 다루는 것보다 작은 규모로 나누어서 함수당 하나의 역할을 담당 하도록 한다.
1 | // reducer 1 |
최종적으로, 위와 같이 잘게 나눈 reducer를 통합하여 전체적인 reducer 기능을 하는 루트 reducer를 생성한다.
1 | function todoApp(state = {}, action) { |