Flux는 Facebook에서 만든 client-side web applications을 구축할 때 사용하는 application architecture(앱 구조), design pattern(디자인 패턴)이다.
MVC (Model–View–Controlle)구조 의 단점을 보완할 목적으로 개발된 Flux는 대규모 프로젝트에서 너무 복잡해지는 MVC구조의 단점을 보완하는 단방향 데이터 흐름(unidirectional data flow)의 구조이다.
action -> dispatcher -> store -> view. 위의 순서대로 작업이 일어나게된다.
action을 통해 이벤트가 일어나게 되면, dispatcher가 action을 통제하여 store에 업데이트를 한다. store에서 업데이트된 데이터를 토대로 현재 view가 수정되어야할경우 리렌더링이 일어난다.
dispatcher는 작업이 중첩되면 안된다.
리덕스(Redux)는 Javascript app을 위한 예측가능한(predictable) state container이다. 리액트 뿐만 아니라 Augular, jQuery, vanilla JavaScript 등 다양한 framework와 작동되게 설계되었다. 즉, 리액트만을 위한 Library는 아니다.
단 하나의 store
를 사용한다. flux
를 토대로 만들어진 라이브러리이지만, 여러개의 store
를 사용하는 flux
와 다르게 단 하나의 store
만 사용하며, 이 안에 모든 state
값들이 포함되어있다. 그래서 ‘전지전능한 진리’이다. store
에는 데이터가 nested 된 구조로 이루어져있다.
스토어는 언제나 단 한개이다. 스토어를 여러 개 생성해서 상태를 관리하면 안된다. 그 대신 리듀서를 여러 개를 만들어서 관리 할 수 있다.
store
안에 있는 모든 state
값들은 ‘읽기’전용 데이터들이다. 컴포넌트에서 직접 state
값을 수정할 수 없다. 이를 가능하게 하려면 action > dispatcher
구조를 통해야한다. 객체를 통해 ‘어떠한’ 변화를 ‘무슨’ 값들과 함께 일어날지 store
에 dispatch
해줌으로 써 state
들을 변경하게된다.
리덕스의 상태, state는 읽기 전용이다. 이 값은 절대로 직접 수정하면 안된다. 직접 수정을 하게 된다면 구독 함수를 제대로 실행하지 않거나 컴포넌트의 리렌더링이 되지 않을 수 있다.
- 상태를 업데이트 할 때는 언제나 새 상태 객체를 만들어서 넣어 주어야 한다. 업테이트를 할 때마다 새 객체를 만든다면 메모리 누수가 일어나지 않을까? 아니다. 이전에 사용하던 객체들이 메모리에 누적되지 않는다. 상태 레퍼런스가 사라지면 자동으로 메모리 관리를 한다.
위에서 설명했듯이 state
를 변경하려면 action > dispatcher
를 통해야만한다. 이때 받아온 action
객체를 처리하는 것이 reducer
입니다. action
은 어떤 변화가 일어날지를 알려준다면 reducer
는 이때 받은 값을 토대로 변화를 일으킵니다. reducer
에 대해선 다음과 같습니다
- 외부 네트워크 혹은 데이터베이스에 접근하지 않아야한다.
return
값은 오직parameter
값에만 의존되어야한다.- 인수는 변경되지 않아야한다.
- 같은 인수로 실행된 함수는 언제나 같은 결과를 반환해야한다.
- 순수하지 않은
API
호출을 하지 말아야 한다. (Date
및Math
의 함수 등)- 모든 변화는 순수 함수로 구성해야 한다. 여기에서 함수는 리듀서 함수를 말한다. 순수 함수에서 결과값을 출력할 때는 파라미터 값에만 의존해야 하며, 같은 파라미터는 언제나 같은 결과를 출력해야 한다.
- 예를 들어 리듀서 함수 내부에서 외부 네트워크와 데이터베이스에 직접 접근하면 안된다. 요청 실패 할 수도 있고, 외부 서버의 반환 값이 변할 수 있기 때문이다.
액션(Action) : 상태 변화를 일으킬 때 참조하는 객체이다.
const mapActionToProps = (dispatch) => { }
함수 사용Action
이라는 단어는 Event
와 같아고 생각하면 된다.dispatch
인수에서 Reducer
로 넘길 객체(type)를 정의한다.Action
이 실행되고 끝나면 type
을 반환하는데 type
은 Reduce로 전달된다.스토어(Store) : 애플리케이션의 상태 값들을 내장하고 있다.
state
값을 가지고 있다.state
의 값이 변경된다.리듀서(Reducer): 상태를 변화시키는 로직이 있는 함수이다.
export function reducer(state = {state : 10, age:100}, action)
Reducer
함수를 생성 할 때 살찐 에로우를 사용하지 않는다.Reducer
함수는 순수함수여야 한다. 결과 값을 출력 할 때는 파라미터 값에만 의존해야 하며, 언제나 같은 결과를 출력해야 한다.Reducer
에서 state
를 사용한다면 반드시 state
를 초기화 해야 한다.Reducer
에서 state
의 변화가 일어난다.reducer
에서 해야 한다.State : 컴포넌트에 최종 출력하기 전 거치는 중간과정이다.
mpaStateToProps(state)
함수 사용state
는 store
에서 가져왔다 라고 생각하면 된다.Store
에 저장되어 있는 변수를 가져와서 최종 가공을 위한 목적으로 사용된다.num:state.num*100
이라고 갱신을 하더라도 실제 num의 값은 갱신되지 않고 컴포넌트에 출력하는 값을 가공한 것이다.디스패치(dispatch) : 액션을 스토어에 전달하는 것을 의미한다.
구독 : 스토어 값이 필요한 컴포넌트는 스토어를 구독한다.
react-redux
의 connect
함수가 대신 한다.subscribe
, unsubscribe
함수를 사용하여 구독 및 구독 취소를 할 수 있다.리덕스는 Flux의 몇 가지 중요한 특성에 영감을 받아 개발되었다.
Flux와 달리 리덕스는 dispatcher
라는 개념이 존재하지 않는다. 그리고 다수의 store
도 존재하지 않는다. 대신 리덕스는 하나의 root
에 하나의 store
만이 존재하고, 순수함수(pure functions)에 의존하는데, 이 순수 함수는 이것들을 관리하는 추가적인 entity
없이도 조합하기 쉽다 .
물론 차이점이 있지만, 결론적으로 Flux
, 리덕스 두 가지의 구조 모두 닮았으며 결국 리덕스는 Flux
패턴을 좀 더 쉽고 정돈된 형태로 쓸 수 있게 도와주는 라이브러리라고 볼 수 있다. 서로 영향을 많이 주고받았기 때문에 리덕스를 공부할 때 Flux
구조를 공부하는 것은 많은 도움이 될 것이다.
리액트로 프로젝트를 진행하게 되면, Component는 local state를 가지게 되고, 어플리케이션은 global state를 가지게 된다.
Local state: 각각의 Component가 가지는
state
. 어플리케이션은 이state
를 기반으로 만들어진다.Global state: 예를 들어, 유저의 로그인의 유무에 따라 어플리케이션의 state가 달리 보이는 것을 들 수 있다. 어플리케이션 전체에서
global state
는 유지, 즉local state
는global state
를 공유하게 되는 것이다.
리액트로만 프로젝트를 진행하게 될 경우 우리의 어플리케이션은 local state
, 그리고 global state
를 관리하기 어렵다. 이 때 생기는 몇가지 문제를 가지고 왜 리덕스가 필요한지 설명해보도록 하겠다.
이전 프로젝트 에서는 최상단 Component
인 App.js
에서 cart state
를 만들고, 각각의 Component
에 이를 props
로 전달하여 프로젝트를 관리했었다.
프로젝트의 구조가 굉장히 단순하였기 때문에 단 한번의 data
이동으로 cart state
가 전달되었다. 하지만, 프로젝트의 규모가 커지고 Component
의 수가 늘어나게 된다면 어떻게 될까?
단순히 cart state
를 CartItem.js
로 전달하고 싶은데, 이를 위해 cart props
를 사용하지 않는 Component
에도 cart props
를 전달하게 된다. 프로젝트의 규모가 커질수록, props
로 data
를 전달하기 위해 이렇게 필요없는 data
의 흐름이 생기게 된다. 또한, 만약 CartItem.js
에서 제대로 cart props
가 전달이 안 될 경우, 중간에 끼인 모든 Component
에서 일일이 문제점을 찾아봐야 한다.
대부분 어플리케이션에서는 로그인 기능이 구현되어야 한다. 유저마다의 개인정보, 그리고 결제정보 등이 있어야 되기 때문이다. 하지만 리액트만으로 프로젝트를 진행하면, global state
를 모든 Component
에 유지하기 어렵다. 유저의 인증정보를 모든 Component
에 props
로 계속해서 전달해야 하는데, 하단의 diagram
보다 훨씬 복잡한 절차를 거치게 될 것이다.
3 Core things of Reducer
- 할 일을 정의하는
Action
(인수는 옵션)- 애플리케이션의 모든 데이터를 저장하는
state
state
와Action
을 받아 새 상태를 리턴하는Reducer
payload
는 종류와 상관 없이 객체가 될 수 있다.Reducer
에는 타입 T
의 state
와 Action
을 받아 새 state
를 리턴하는 함수가 포함된다.interface Action {
type: string;
payload?: any;
}
interface Reducer<T> {
(state: T, action: Action): T;
}
let reducer: Reducer<number> = (state: number, action: Action) => {
return state;
};
console.log(reducer(0, null)); //0
리덕스에서는 상태를 변경할 수 없다는 사실을 잊지 말자.
switch
의 default case
에서는 원래 state
를 리턴한다. 그래야 알 수 없는 동작이 전달되어도 오류가 출력되지 않고 원래 state
가 변경되지 않는다.
let incrementAction: Action = { type: 'INCREMENT' };
let decrementAction: Action = { type: 'DECREMENT' };
let reducer: Reducer<number> = (state: number, action: Action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
case 'PLUS':
return state + action.payload;
default:
return state;
}
};
console.log(reducer(3, { type: 'PLUS', payload: 7 })); // 10
console.log(reducer(5, { type: 'PLUS', payload: -2 })); // 3
리듀서는 순수한 함수이며, 외부 환경을 변경하지 않는다. 문제는 앱에서 모든 것이 변경된다는 점이다. 즉 상태는 변화하고 앱 어딘가에서는 새 상태를 유지하고 있어야 한다.
리덕스에서는 상태를 저장소(store)에 보관한다. 저장소는 리듀서를 실행하여 새 상태를 유지할 책임을 진다.
class Store<T> {
private _state: T;
constructor(private reducer: Reducer<T>, initialState: T) {
this._state = initialState;
}
getState(): T {
return this._state;
}
dispatch(action: Action): void {
this._state = this.reducer(this._state, action);
}
}
let store = new Store<number>(reducer, 0);
console.log(store.getState()); // 0
store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // 1
store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // 2
store.dispatch({ type: 'DECREMENT' });
console.log(store.getState()); // 1
constructor
에서는 _state
를 초기 상태로 설정했다.
getState()
는 단순히 현재 _state
를 리턴한다.
dispatch
는 동작을 받아 이를 리듀서로 보낸 뒤 _state
의 값을 리턴값으로 업데이트한다.
dispatch
는 아무것도 리턴하지 않는다. 저장소의 상태를 '업데이트'할 뿐이다. (리덕스의 중요한 원칙)
다음의 글을 참고하였습니다.
리덕스 공부중인데 큰 도움이 되었습니다.
한 가지 궁금한 질문이 있습니다.
action 설명에서 action > dispatcher 구조로 통해야만 reducer로 보내어 상태값을 바꿀 수 있다고 되어 있는데
flux 비교 설명에서는 리덕스는 flux랑 달리 dispathcer가 없다라고 되어 있어 헷갈려서요...
감사합니다 !!