Thunk(1)[심화_4]
Redux 미들웨어
리덕스의 핵심 요소 :
- Action: State가 변하는것. “무엇이 일어날지”
- Reducer: 변화를 일으키는, 즉 데이터(state)를 수정하는 함수. action을 통해 어떠한 행동을 정의했다면, 그 결과 어플리케이션의 상태가 어떻게 바뀌는지는 특정하게 되는 함수이다.
- Store: action과 action에 따라 상태를 수정하는 reducer를 저장하는 어플리케이션에 있는 단 하나의 객체. 스토어는 State 를 수시로 확인해 View 한테 변경된 사항을 알려주는 역할을 한다.
- Dispatch: 스토어의 내장 함수 중 하나로 리듀서에게 Action 을 발생하라고 시키는 것 store에서 reducer함수를 실행시켜 state를 업데이트한다.
- Subscribe: 액션이 디스패치 될 때 마다 전달해준 함수를 호출한다.
- Middleware: 액션을 디스패치 했을때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행한다.
thunk 와 saga 가 대표.
3가지 대표 룰 :
- 하나의 어플리케이션은 하나의 Store만 가진다.
- 리듀서는 순수함수이다.
동일안 파라미터로 호출 된 리듀서는 언제나 같은 패턴의 결과값을 반환해야만 한다. - state는 read-only 이다
기존의 state 고유 값은 수정하지 않고 새로운 state 를 만들어 이를 수정하는 방식으로 업데이트를 한다.이는 리덕스 고유의 불변성을 지키기 위함임.
(1) 미들웨어란?
- View 에서 action이 일어난다.
- dispatch 에서 action이 일어나게 된다.
<button onClick={() => dispatch(__postTodo(newTodo))}>
- (action에 의한 reducer 함수가 실행되기 전에 middleware가 작동한다.)
export const __postTodo = createAsyncThunk(
"todo/postTodo",
async (payload, thunkAPI) => {
try {
const data = await axios.post("http://localhost:3001/todo", payload);
return thunkAPI.fulfillWithValue(data.data);
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
}
);
- reducer 함수를 실행한다.
extraReducers: {
[__postTodo.pending]: (state) => {
state.isLoading = true;
},
},
}),
- reducer 의 실행결과 store에 새로운 값을 저장한다.
- store의 state에 subscribe 하고 있던 UI에 변경된 값을 준다.
리덕스에서 dispatch를 하면 -> action 이 리듀서로 전달이 되고 -> 리듀서는 새로운 state를 반환합니다.
근데 미들웨어를 사용하면 이 과정 사이에 우리가 하고 싶은 작업들을 넣어서 할 수 있습니다.
만약 counter 프로그램에서 더하기 버튼을 클릭했을 때 바로 +1를 더하지 않고 3초를 기다렸다가, +1이 되도록 구현하려면 미들웨어를 사용하지 않고서는 구현할 수 없습니다. 왜냐하면 dispatch가 되자마자 바로 action이 리듀서로 달려가서 새로운 state를 반환해버리기 때문이죠. 즉 여기서 “3초를 기다리는 작업" 이 작업을 미들웨어가 해주는 것 입니다.
보통 우리가 리덕스 미들웨어를 사용하는 이유는 서버와의 통신을 위해서 사용하는 것이 대부분이고, 또한 그 중에서도 많이 사용되고 있는 리덕스 미들웨어는 Redux-thunk 라는 것이 있습니다. 이것을 직접 실습해보면서, 미들웨어에 대해 이해해봅시다.
thunk
(1) thunk 소개
리덕스 thunk란,
리덕스에서 많이 사용하고 있는 미들웨어중에 하나입니다. thunk를 사용하면 우리가 dispatch를 할때 객체가 아닌 함수를 dispatch 할 수 있게 해줍니다. 즉 dispatch(객체) 가 아니라 dispatch(함수)를 할 수 있게 되는 것이죠!
그래서 중간에 우리가 하고자 하는 작업을 함수를 통해 넣을 수 있고, 그것이 중간에 실행이 되는 것 입니다. 그래서 아래 흐름과 같이 실행이 되는거죠. 그리고 이 함수를 thunk 함수라고 부릅니다.
dispatch(함수) → 함수실행 → 함수안에서 dispatch(객체)
(2) thunk 사용하기
우리는 아래 순서대로 구현을 할 것 입니다.
- 우리의 첫 thunk 함수 만들기
- extraReducer에 thunk 등록하기
- dispatch(thunk 함수) 하기
- 테스트
(3) 우리의 첫 thunk 함수
thunk 함수를 만들어봅시다.
thunk 함수의 역할은 “3초를 기다리는 것” 입니다. 그리고 3초가 지나면 원래 하려고 했던 ADD_NUMBER를 해주는 것 까지가 thunk함수가 해야 할 일 입니다.
툴킷에서는 createAsyncThunk 라는 API를 사용해서 thunk 함수를 생성할 수 있습니다. 이 API는 함수인데, 첫번째 인자에는 Action Value, 두번째 인자에는 함수가 들어갑니다. 이 함수에 우리가 하고 싶은 작업들을 구현하면 됩니다.
두번째 들어가는 함수에서도 인자를 꺼낼 수 있는데, 첫번째 인자(arg)는 이 thunk함수가 외부에서 사용되었을 때 넣은 값을 여기에서 조회할 수 있고, 두번째 인자에서는 thnuk가 제공하는 여러가지 API 기능들이 담긴 객체를 꺼낼 수 있습니다. 일단 이 부분은 이해가 되지 않으면 넘어가도 됩니다. 뒤에서 실제로 구현된 코드를 보면 아하! 하실것이니까요.
export const __addNumber = createAsyncThunk(
"ADD_NUMBER_WAIT",
(arg, thunkAPI)=>{},
);
- thunk 함수는 createAsyncThunk 라는 툴킷 API를 사용해서 생성합니다.
- __가 함수 이름에 붙는 이유는 이 함수가 thunk 함수라는 것을 표시하기 위한 개인의 convention 입니다. 함수의 이름은 본인이 편한 이름으로 명명하세요.
우리가 원래 하려고 했던 3초를 기다리는 thunk 함수를 만들어볼게요. 아래 코드를 참고해주세요.
첫번째 자리에는 action value를 넣었습니다. 그리고 두번째에는 함수를 넣었어요.
함수안에는 setTimeout 라는 Web API를 이용해서 3초를 기다리게 했고, 이후에 thunkAPI 안에 있는 dispatch를 통해서 우리가 원래 하려고 했던 addNumber라는 action creator를 넣었습니다.
// 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;
arg는 컴포넌트에서 사용자가 input에 입력한 더하고자 하는 값입니다. 예전에 우리가 구현했던 것인데요, 이런식으로 payload 값을 받아올 수 있습니다. 이 코드가 어떻게 동작하는지 한번 확인해볼까요?
컴포넌트에서의 코드는 아래와 같습니다. 기존에는 addNumber 라는 action creator를 dispatch했다면, 이제는 __addNumber 라는 thunk함수를 dispatch 해줍니다.
App.jsx에 아래 코드를 작성해봅시다.
// src/App.jsx
import React from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { minusNumber, __addNumber } from "./redux/modules/counterSlice";
const App = () => {
const dispatch = useDispatch();
const [number, setNumber] = useState(0);
const globalNumber = useSelector((state) => state.counter.number);
const onChangeHandler = (evnet) => {
const { value } = evnet.target;
setNumber(+value);
};
// thunk 함수를 디스패치한다. payload는 thunk함수에 넣어주면,
// 리덕스 모듈에서 payload로 받을 수 있다.
const onClickAddNumberHandler = () => {
dispatch(__addNumber(number));
};
const onClickMinusNumberHandler = () => {
dispatch(minusNumber(number));
};
return (
<div>
<div>{globalNumber}</div>
<input type="number" onChange={onChangeHandler} />
<button onClick={onClickAddNumberHandler}>더하기</button>
<button onClick={onClickMinusNumberHandler}>빼기</button>
</div>
);
};
export default App;
최종정리
- 리덕스 미들웨어를 사용하면, 액션이 리듀서로 전달되기전에 중간에 어떤 작업을 더 할 수있다.
- Thunk를 사용하면, 객체가 아닌 함수를 dispatch 할 수 있게 해준다. [thunk의 핵심]
- 리덕스 툴킷에서 Thunk 함수를 생성할 때는 **createAsyncThunk 를 이용한다.**
- **createAsyncThunk() 의 첫번째 자리에는 action value, 두번째에는 함수가 들어간다.**
- 두번째로 들어가는 함수에서 2개의 인자를 꺼내 사용할 수 있는데, 첫번째 인자는 컴포넌트에서 보내준 payload이고, 두번째 인자는 thunk에서 제공하는 여러가지 기능이다.
- dispatch: thunk 함수안에서 dispatch를 할 때 사용
- getState: thunk 함수안에서 현재 리덕스 모듈의 state 값을 사용하고 싶을 때 사용