'Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너입니다'
Redux는 여러분이 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고, 테스트하기 쉬운 앱을 작성하도록 도와줍니다. 여기에 더해서 시간여행형 디버거와 결합된 실시간 코드 수정과 같은 훌륭한 개발자 경험을 제공합니다.
Redux의 정의는 공식문서에서 이렇게 말하고 있다.
자바스크립트의 싱글 페이지 어플리케이션이 갖추어야할 요건이 점점 더 복잡해지고있는 만큼, 어느 때 보다도 많은 상태를 자바스크립트 코드로 관리할 필요가 생겨났다.
여기서 상태란 (서버응답, 캐시데이터, 지역적으로 생성해서 사용하고 있지만 아직 서버에 저장되지 않은 데이터를 의미한다. 이외에도 활성화된 라우트, 선택된 탭, 로딩을 보여줄지 여부, 페이지네이션 컨트롤 등 다양한 UI 상태도 포함한다)
Redux는 애플리케이션의 상위에 자리잡은 글로벌 Store를 만들어서 다른 모든 컴포넌트로 State를 공급할 수 있다.
데이터를 전역에서 관리하는데 이를 관리하는 곳이 Store이고 내부에 Action에 따른 데이터 값을 변화시켜주는 순수함수 Reducer가 정의되어 있다.
Redux와 함께 사용한다면 React의 단점을 보완해줄 수 있다. (react의 장점은 직관적이고 관리하기가 편하지만 단점으로는 만약 컴포넌트들이 많아졌을 때 그러니깐 앱의 규모가 커졌을 때는 상위(부모)컴포넌트에서 데이터가 하위컴포넌트로 움직이는 단일 구조적인 부분들이 관리할 때 불필요한 복잡성이 생길 수 있다.)
애플리케이션의 모든 상태는 하나의 저장소 안에 하나의 객체 트리구조로 저장됩니다.
: 하나의 코드 베이스로 다양한 환경에서 실행 가능한 코드(universal application)을 쉽게 만들 수 있고, 하나의 상태 트리만을 가지고 있기 때문에 디버깅에도 용이하고, 빠른 개발 사이클을 위해 개발중인 애플리케이션의 상태를 저장해 놓을 수도 있다. 하나의 상태 트리만을 가지고 있기 때문에 실행취소/다시실행(undo/redo)를 손쉽게 구현할 수 있다.
console.log(store.getState());
{
visibilityFilter: 'SHOW_ALL',
todos: [{
text: 'Consider using Redux',
completed: true,
}, {
text: 'Keep all state in a single tree',
completed: false
}]
}
상태를 변화시키는 유일한 방법은 무슨 일이 벌어지는 지를 묘사하는 액션 객체를 전달하는 방법뿐입니다.
: 모든 상태 변화는 중앙에서 관리되며 모든 액션은 엄격한 순서에 의해 하나하나 실행되기 때문에 신경써서 관리해야할 미묘한 경쟁 상태는 없다. 액션은 그저 평범한 객체일 뿐이다. 따라서 기록을 남길 수도 시리얼라이즈(직렬화)할 수 있으며, 저장할 수 있고, 이후에 테스트나 디버깅을 위해서 재현하는 것도 가능하다.
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
})
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
})
액션에 의해 상태 트리가 어떻게 변화하는 지를 지정하기 위해 프로그래머는 순수 리듀서를 작성해야한다.
: 리듀서는 그저 이전 상태와 액션을 받아 다음 상태를 반환하는 순수 함수이다. 이전 상태를 변경하는 대신 새로운 상태 객체를 생성해서 반환해야한다는 사실은 기억하고 있어야한다. 그래서 처음엔 하나의 리듀서 만으로도 충분하지만, 애플리케이션이 커지면서 상태 트리의 특정한 부분들을 조작하는 더 작은 리듀서들로 나누어 주는 것도 고려해야한다. 리듀서는 그저 함수이기 때문에 호출되는 순서를 정하거나 추가적인 데이터를 넘길 수도 있다. 일반적인 재사용 가능한 리듀서를 작성하는 것도 가능하다.
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.slice(0, action.index),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
]
default:
return state
}
}
import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, todos })
let store = createStore(reducer)
[ type State = any ]
상태 트리 라고도 한다. 넓은 의미의 단어지만 Redux API에서는 보통 저장소에 의해 관리되고, getState()에 의해 반환되는 하나의 상태값을 지칭한다. 상태는 Redux 애플리케이션 전체 상태를 나타내며 보통 깊게 중첩되어있는 객체라고 볼 수 있다.
[type Action = Object]
액션은 상태를 변화시키려는 의도를 표현하는 평범한 객체이다. 액션은 저장소에 데이터를 넣는 유일한 방법이고 UI이벤트에서 왔든, 네트워크 콜백에서 왔던, 웹소켓과 같은 다른 소스에서 왔든 모든 데이터는 액션으로써 보내지게 된다.
액션은 어떤 형태의 액션이 행해질지 표시하는 type을 가져야하는데 type은 상수로 정의되고 다른 모듈에서 import할 수 있다.
[type Reducer<S, A> = (state: S, action: A) => S]
리듀서(리듀싱 함수)는 누적값과 값을 받아서 새로운 누적값을 반환하는 함수인데 이들은 값들의 컬렉션을 받아서 하나의 값으로 줄이는데 사용된다.
리듀서는 Redux만의 개념은 아니고 기본적으로 함수형 프로그래밍에서 왔다고 볼 수 있다. 자바스크립트에서는 리듀싱을 위한 내장 API가 있는데 Array.prototype.reduce()가 해당된다.
Redux에서 누적값은 상태 객체이고, 누적될 값은 액션이다. 리듀서는 주어진 이전 상태와 액션에서 새로운 상태를 계산한다. 이들은 반드시 같은 입력이있으면 같은 출력을 반환하는 순수 함수 여야만 한다.
API호출을 리듀서 안에 넣지 말아야 한다.
[type BaseDispatch = (a: Action) => Action
type Dispatch = (a: Action | AsyncAction) => any]
디스패치 함수는 액션이나 비동기 액션을 받는 함수이다. 받은 다음 하나나 여러개의 액션을 저장소에 보낼 수도 보내지 않을 수도 있다.
기본 디스패치 함수는 반드시 동기적으로 저장소의 리듀서에 액션을 보내야한다. 그러면 리듀서는 저장소가 반환한 이전 상태와 함께 새 상탤르 계산하게된다 이때 리듀서가 사용하기 위해서 액션은 평범한 객체여야만 한다.
그리고 미들웨어가 기본 디스패치 함수를 감싸고 있고, 미들웨어를 통해 디스패치 함수는 액션 뿐만 아니라 비동기 액션을 처리할 수 있다.
[type AsyncAction = any]
비동기 액션은 기본 dispatch() 함수로 전달되기 전에 미들웨어를 통해 액션(이나 일련의 액션들)으로 바뀌어야 한다. 미들웨어에 따라 서로 다른 타입이 될 수도 있다.
[type MiddlewareAPI = { dispatch: Dispatch, getState: () => State }
type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch]
미들웨어는 디스패치 함수를 결합해서 새 디스패치 함수를 반환하는 고차함수이다. 이들은 종종 비동기 액션을 액션으로 전환한다.
미들웨어는 함수 결합을 통해 서로 결합할 수 있다. 이는 액션을 로깅하거나, 라우팅과 같은 부수효과를 일으키거나, 비동기 API 호출을 일련의 동기 액션으로 바꾸는데 유용하다.
[ type Store = {
dispatch: Dispatch,
getState: () => State,
subscribe: (listener: () => void) => () => void,
replaceReducer: (reducer: Reducer) => void
}]
저장소는 애플리케이션의 상태 트리를 가지고 있는 객체이다. 리듀서 수준에서 결합이 일어나기 때문에, Redux 앱에는 단 하나의 저장소만 있어야 한다.
dispatch(action)는 위에서 설명한 기본 디스패치 함수입니다.
getState()는 저장소의 현재 상태를 반환합니다.
subscribe(listener)는 상태가 바뀔 때 호출될 함수를 등록합니다.
replaceReducer(nextReducer)는 핫 리로딩과 코드 분할을 구현할때 사용됩니다. 여러분이 사용할 일은 별로 없습니다.
참고 레퍼런스 :
https://ko.redux.js.org/
https://jeffgukang.github.io/react-native-tutorial/
https://react.vlpt.us/