Redux 공식문서에 따르면 Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너이다. 그리고 우리가 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고, 테스트하기 쉬운 앱을 작성하도록 도와준다.
Redux는 단방향 데이터 흐름 구조를 가진다.
import { createStore } from 'redux'
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
let store = createStore(counter)
store.subscribe(() => console.log(store.getState())))
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
위의 기본 예제를 보면, 앱의 상태 전부는 하나의 저장소(store)안에 있는 객체 트리에 저장된다. 상태 트리를 변경하는 유일한 방법은 무엇이 일어날지 서술하는 객체인 액션(action)을 보내는 것 뿐이고, 액션이 상태 트리를 어떻게 변경할지 명시하기 위해 우리는 리듀서(reducers)를 작성해야 한다고 한다.
즉, 우리가 Redux를 사용하게 되면, 상태를 바로 변경하는 대신, 액션이라 불리는 평범한 객체를 통해 일어날 변경을 명시한다. 그리고 각각의 액션이 전체 애플리케이션의 상태를 어떻게 변경할지 결정하는 특별한 함수인 리듀서를 작성한다.
보통의 Redux 앱에는 하나의 루트 리듀서 함수를 가진 단 하나의 저장소인 store가 있다. 앱이 커짐에 따라 루트 리듀서를 상태 트리의 서로 다른 부분에서 개별적으로 동작하는 작은 리듀서들로 나눌 수 있다.
그리고 dispatch를 통해 store가 업데이트 되고 그리고 그 값을 나중에 후술할 selector로 받고 반영할 수 있다.
즉 Redux는 state를 관리한다고 할 수 있다.
위의 구조는 Flux 구조라고 한다. Flux 구조의 가장 큰 특징은 단방향 데이터 흐름이다. 데이터의 흐름은 디스패쳐 => 스토어 => 뷰 순서 이며 뷰에서 입력이 발생하면 액션을 통해서 디스패쳐로 향하게 된다.
즉 Redux는 Flux 구조를 따른다고 볼 수 있다.
디스패쳐를 통해 스토어에 변화를 일으킬 수 있는데 이때 디스패쳐의 데이터 묶음을 액션이라고 한다.
dispatch({ <= 디스패쳐
type: GET_POST, <= 액션 이름
// GET_POST을 통해 스토어에 변화가 일어남
})
디스패쳐는 Flux 어플리케이션의 모든 데이터 흐름을 관리하는 일종의 허브 역할이다. 액션이 발생하면 디스패처로 메세지나 액션 객체나 전달되고 디스패쳐에서는 이러한 메세지 혹은 액션 객체를 콜백 함수를 통해 스토어로 전달한다. 액션을 통해 스토어에 접근하려면 반드시 디스패쳐를 통해서 접근해야 한다.
스토어는 애플리케이션의 상태를 저장한다. 모든 상태 변경은 스토어에 의해 결정되며 상태 변경을 위한 요청을 스토어에 직접 할 수는 없다. 반드시 액션을 통해 디스패쳐를 거친 후 액션을 보내야만 상태값이 변경된다.
뷰는 상태를 가져와서 보여주고 사용자로 부터 입력 받을 화면을 보여준다. 컨트롤러 뷰는 스토어와 뷰의 중간 관리자 같은 역할을 하고 스토어에서 상태 값 변경이 일어났을 때 스토어는 그 사실을 컨트롤러 뷰에서 전달하고 컨트롤러 뷰는 자신 아래에 모든 뷰에게 새로운 상태를 넘겨준다.
액션과 리듀서를 하나로 모으는 객체 저장소인 store는 애플리케이션의 전체 상태 트리를 보유한다. 내부 상태를 변경하는 유일한 방법은 해당 상태에 대한 action을 store에 전달하는 방법 뿐이다.
단, redux store는 클래스가 아니다. 단지 몇가지의 method가 있는 객체일 뿐이다.
dispatch는 store의 내장 함수중 하나이다. 디스패치는 액션을 발생 시키는 것 이라고 이해하면 된다.
dispatch 라는 함수는 액션을 파라미터로 전달한다. ex) dispatch(actions) 등
그렇게 호출을 하면, 스토어는 리듀서 함수를 실행시켜서 해당 액션을 처리하는 로직이 있다면 액션을 참고하여 새로운 상태를 만들어 준다.
Action은 간단한 javascript 객체다. action에는 우리가 수행하는 작업의 유형을 지정하는 type 속성과 선택적으로 redux 저장소에 일부 데이터를 보내는데 사용되는 payload 속성이 존재한다.
{type:'deposit', payload:10}
action은 액션 생성함수를 통해 만들 수 있다.
export function addTodo(data) {
return {
type: "ADD_TODO",
data
};
}
// 화살표 함수로도 만들 수 있습니다.
export const changeInput = text => ({
type: "CHANGE_INPUT",
text
});
액션 생성함수를 만들어서 사용하는 이유는 나중에 컴포넌트에서 더욱 쉽게 액션을 발생시키기 위함이다.
Reducer는 애플리케이션 상태의 변경 사항을 결정하고 업데이트된 상태를 반환하는 함수다. reducer는 인수로 조치를 취하고 store 내부의 상태를 업데이트한다.
리듀서는 이전 state와 액션 객체를 받아서 새 state를 리턴한다.
(previousState, action) => newState
리듀서는 순수함수이기에 지켜야 할 것들이 있다.
1. 리듀서 함수는 이전 상태와, 액션 객체를 파라미터로 받는다.
2. 이전의 상태는 절대로 건들이지 않고, 변화를 일으킨 새로운 상태 객체를 만들어서 반환
3. 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과값을 반환해야만 한다. new Date(), 랜덤 숫자 생성, 네트워크에 요청 등은 순수하지 않은 작업(?) -> 리듀서 밖에서 처리