12주차 WIL (추가 공부 : Flux pattern / Thunk )
기존에는 데이터를 가지고 있는 모델이 렌더링을 하기 위해 뷰레이어를 데이터로 보냈는데,
사용자의 상호작용이 뷰를 통해 일어났기에 사용자의 입력에 따라 뷰가 가끔씩 모델을 업데이트 해야할 필요가 있었다.
또한, 의존성() 때문에 모델이 다른 모델을 업데이트 해야할 때도 있었다.
이 외에도 이런 상황때문에 여러 연결된 다른 변경들을 초래하기도 했다.
또한 이런 변경들은 비동기적으로 생길 수도 있었고 하나의 변경이 다수의 변경들을 일으킬 수 있게 된다.
이러한 상황들을 데이터흐름의 디버그하기 어렵게 만든다.
그래서 마련한 해결책 : 단방향 데이터 흐름
위의 아키텍쳐를 Flux라고 부르게 된다.
액션 생성자(the action creator)
액션 생성자가 하는 일은 마치 전보기사(telegraph operator)와 같다. 무슨 메시지를 보낼지 알려주면 액션 생성자는 나머지 시스템이 이해할 수 있는 포맷으로 바꿔준다 — [전보기사가 알파벳을 기계들이 처리할 수 있는 모스부호로 바꾸는 것처럼].
액션 생성자는 타입(type)과 페이로드(payload)를 포함한 액션을 생성한다. 타입은 시스템에 정의 된 액션들(일반적으로 상수들) 중의 하나이다.
모든 가능한 액션들을 아는 시스템을 가짐으로써 부차적으로 갖는 멋진 효과가 있다. 새로운 개발자가 프로젝트에 들어와서 행동 생성자 파일을 열면 시스템에서 제공하는 API 전체 — 모든 가능한 상태변경 — 를 바로 확인할 수가 있다는 점이다.
일단 액션 생성자가 액션 메시지를 생성한 뒤에는 디스패쳐(dispatcher)로 넘겨준다.
디스패쳐(dispatcher)
디스패쳐는 기본적으로 콜백(callback)이 등록되어있는 곳이다.
이것은 마치 전화 교환대에서 교환원이 일하는 것과 같다 — [전화 교환대에서는 등록된 모든 전화들과의 연결이 가능하다]. 디스패쳐는 액션을 보낼 필요가 있는 모든 스토어(store)를 가지고 있고, 액션 생성자로부터 액션이 넘어오면 여러 스토어에 액션을 보낸다.
이 처리는 동기적으로(synchronously) 실행되어서 위쪽에서 이야기했던 다수의 공으로 플레이하는 Pong 게임같은 경우를 처리하는데 도움을 준다. 만약 스토어들 사이에 의존성(dependency)이 있어서 하나를 다른 것보다 먼저 업데이트를 해야한다면, waitFor()를 사용해서 디스패쳐가 적절히 처리하도록 할 수 있다.
Flux의 디스패쳐는 다른 아키텍처들과는 조금 다른 점이 있다. 바로 액션 타입과는 관계없이 등록된 모든 스토어로 보내진다는 점이다. 이말인 즉슨, 스토어가 특정 액션만 구독(subscribe)하지 않고 모든 액션을 일단 받은 뒤 처리할지 말지를 결정한다는 뜻이다.
스토어(store)
스토어는 마치 모든 것을 관리하는 정부관료와 같다. 모든 상태 변경은 반드시 스토어에 의해서 결정되어야만 하며, 상태 변경을 위한 요청을 스토어에 직접 보낼 순 없다. 스토어에는 설정자(setter)2가 존재하지 않으므로, 상태 변경을 요청하기 위해서는 반드시 모든 정해진 절차를 따라야만 한다… 다시말해, 무조건 액션 생성자/디스패쳐 파이프라인을 거쳐서 액션을 보내야만 한다.
위에서도 설명했듯이, 만약 스토어가 디스패쳐에 등록되어 있다면, 모든 액션을 받게 될 것이다. 스토어의 내부에서는 보통 switch statement를 사용해서 처리할 액션과 무시할 액션을 결정하게 된다. 만약 처리가 필요한 액션이라면, 주어진 액션에 따라서 무엇을 할 지 결정하고 상태를 변경하게 된다.
일단 스토어에 상태 변경을 완료하고 나면, 변경 이벤트(change event)를 내보낸다. 이 이벤트는 컨트롤러 뷰(the controller view)에 상태가 변경했다는 것을 알려주게 된다.
컨트롤러 뷰(the controller view)와 뷰(the view)
뷰는 상태를 상태를 가져오고 유저에게 보여주고 입력받을 화면을 렌더링하는 역할을 맡는다.
뷰는 발표자와 같다. 애플리케이션 내부에 대해서는 아는 것이 없지만, 받은 데이터를 처리해서 사람들이 이해할 수 있는 포맷(HTML)으로 어떻게 바꾸는지 알고 있다.
컨트롤러 뷰는 스토어와 뷰 사이의 중간관리자같은 역할을 한다.
Thunk
리덕스 thunk란,
리덕스에서 많이 사용하고 있는 미들웨어중에 하나.
thunk를 사용하면 우리가 dispatch를 할때 객체가 아닌 함수를 dispatch 할 수 있게 해줌.
즉 dispatch(객체) 가 아니라 dispatch(함수)를 할 수 있게 되는 것.
그래서 중간에 우리가 하고자 하는 작업을 함수를 통해 넣을 수 있고, 그것이 중간에 실행이 되는 것 이며,
그래서 아래 흐름과 같이 실행이 된다. 그리고 이 함수를 thunk 함수라고 부른다.
dispatch(함수) → 함수실행 → 함수안에서 dispatch(객체)
// src/redux/modules/counterSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export const __addNumber = createAsyncThunk(
// 첫번째 인자 : action value
"addNumber",
// 두번째 인자 : 콜백함수
(payload, thunkAPI) => {
setTimeout(() => {
thunkAPI.dispatch(addNumber(payload));
}, 3000);
}
);
const initialState = {
number: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
export const { addNumber, minusNumber } = counterSlice.actions;
export default counterSlice.reducer;
기존에는 addNumber 라는 action creator를 dispatch했다면, 이제는 __addNumber 라는 thunk함수를 dispatch 해준다
- 리덕스 미들웨어를 사용하면, 액션이 리듀서로 전달되기전에 중간에 어떤 작업을 더 할 수있다.
- Thunk를 사용하면, 객체가 아닌 함수를 dispatch 할 수 있게 해준다. [thunk의 핵심]
- 리덕스 툴킷에서 Thunk 함수를 생성할 때는 **createAsyncThunk 를 이용한다.**
- **createAsyncThunk() 의 첫번째 자리에는 action value, 두번째에는 함수가 들어간다.**
- 두번째로 들어가는 함수에서 2개의 인자를 꺼내 사용할 수 있는데, 첫번째 인자는 컴포넌트에서 보내준 payload이고, 두번째 인자는 thunk에서 제공하는 여러가지 기능이다.
- dispatch: thunk 함수안에서 dispatch를 할 때 사용
- getState: thunk 함수안에서 현재 리덕스 모듈의 state 값을 사용하고 싶을 때 사용