천진난만 코딩 스토리
2023.03.13) 항해 36일차 (1-미들웨어, thunk) 본문
지금 이 내용들은 "아 이렇게 쓰는거구나.", "이런식으로 사용하면 되겠네"라는 이해는 되지만
막상 과제에서는 잘 적용할 수 있을지 모르겠다.
사실 걱정되는 것이 이번 주차는 저번 주차의 과제를 하고 강의를 늦게 보기 시작해서 속도가 많이 느리다...
그래서 더 분발하고 있지만 내용이 어려운 만큼 시간이 부족한 것 또한 사실이다..
그래서 과제 두 개를 모두 해낼 수 있을지도 모르겠다.
그렇지만 일단 기죽지는 않기로 했다!!
1) Redux 미들웨어
① 미들웨어란?
리덕스에서 dispatch를 하면 action 이 리듀서로 전달이 되고, 리듀서는 새로운 state를 반환한다.
근데 미들웨어를 사용하면 이 과정 사이에 우리가 하고 싶은 작업들을 넣어서 할 수 있다.
만약 counter 프로그램에서 더하기 버튼을 클릭했을 때 바로 +1를 더하지 않고 3초를 기다렸다가,
+1이 되도록 구현하려면 미들웨어를 사용하지 않고서는 구현할 수 없다.
왜냐하면 dispatch가 되자마자 바로 action이 리듀서로 달려가서 새로운 state를 반환해버리기 때문이다.
즉, 여기서 “3초를 기다리는 작업" 이 작업을 미들웨어가 해주는 것이다.
보통 우리가 리덕스 미들웨어를 사용하는 이유는 서버와의 통신을 위해서 사용하는 것이 대부분이다.
2) thunk
① thunk란
리덕스에서 많이 사용하고 있는 미들웨어중에 하나이다.
thunk를 사용하면 우리가 dispatch를 할때 객체가 아닌 함수를 dispatch 할 수 있게 해준다.
즉 dispatch(객체) 가 아니라 dispatch(함수)를 할 수 있게 되는 것이다! 와우!!
그래서 중간에 우리가 하고자 하는 작업을 함수를 통해 넣을 수 있고, 그것이 중간에 실행이 되는 것이다.
그래서 아래 흐름과 같이 실행이 되는 것이고 , 이 함수를 thunk 함수라고 부른다.
dispatch(함수) → 함수실행 → 함수안에서 dispatch(객체)
② thunk 사용하기
툴킷에서는 createAsyncThunk 라는 API를 사용해서 thunk 함수를 생성할 수 있다.
이 API는 함수인데, 첫번째 인자에는 Action Value, 두번째 인자에는 함수가 들어간다.
이 함수에 우리가 하고 싶은 작업들을 구현하면 된다.
두번째로 들어가는 함수에서 2개의 인자를 꺼내 사용할 수 있는데,
첫번째 인자는 컴포넌트에서 보내준 payload이고, 두번째 인자는 thunk에서 제공하는 여러가지 기능이다.
fulfillWithValue 는 툴킷에서 제공하는 API 이다.
Promise에서 resolve된 경우, 다시 말해 네트워크 요청이 성공한 경우에 dispatch 해주는 기능을 가진 API 이다.
그리고 인자로는 payload를 넣어줄 수 있다.
rejectWithValue 도 툴킷에서 제공하는 API 이다.
Promise가 reject 된 경우, 네트워크 요청이 실패한 경우 dispatch 해주는 기능을 가진 API 이다.
마찬가지로 인자로 어떤 값을 넣을 수 있다.
// thunk 함수는 createAsyncThunk 라는 툴킷 API를 사용해서 생성한다.
// __가 함수 이름에 붙는 이유는 이 함수가 thunk 함수라는 것을 표시하기 위한 개인의 convention이다.
export const __addNumber = createAsyncThunk(
"ADD_NUMBER_WAIT",
(arg, thunkAPI)=>{},
);
③ 코드로 알아보기 1
thunk 함수를 만들어보자!
thunk 함수의 역할은 “3초를 기다리는 것” 이다.
그리고 3초가 지나면 원래 하려고 했던 ADD_NUMBER를 해주는 것 까지가 thunk함수가 해야 할 일 이다.
// 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;
}
},
});
export const { addNumber } = counterSlice.actions;
export default counterSlice.reducer;
모듈에서 thunk로 3초 기다린 후에 실행되도록 한다.
// src/App.jsx
import React from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { __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));
};
return (
<div>
<div>{globalNumber}</div>
<input type="number" onChange={onChangeHandler} />
<button onClick={onClickAddNumberHandler}>더하기</button>
</div>
);
};
export default App;
thunk로 만든 함수를 적용한다.
④ 코드로 알아보기 2
통신 진행중, 실패, 성공
//모듈
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const initialState = {
todos: [],
isLoading: false,
error: null,
};
export const __getTodos = createAsyncThunk(
"todos/getTodos",
async (payload, thunkAPI) => {
try {
const data = await axios.get("http://localhost:4000/todos");
return thunkAPI.fulfillWithValue(data.data);
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
}
);
export const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {},
extraReducers: {
[__getTodos.pending]: (state) => {
state.isLoading = true; // 네트워크 요청이 시작되면 로딩상태를 true로 변경합니다.
},
[__getTodos.fulfilled]: (state, action) => {
state.isLoading = false; // 네트워크 요청이 끝났으니, false로 변경합니다.
state.todos = action.payload; // Store에 있는 todos에 서버에서 가져온 todos를 넣습니다.
},
[__getTodos.rejected]: (state, action) => {
state.isLoading = false; // 에러가 발생했지만, 네트워크 요청이 끝났으니, false로 변경합니다.
state.error = action.payload; // catch 된 error 객체를 state.error에 넣습니다.
},
},
});
export const {} = todosSlice.actions;
export default todosSlice.reducer;
//브라우저
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { __getTodos } from "../redux/modules/todosSlice";
function Second() {
const dispatch = useDispatch();
const { isLoading, error, todos } = useSelector((state) => state.todos);
useEffect(() => {
dispatch(__getTodos());
}, [dispatch]);
if (isLoading) {
return <div>로딩 중....</div>;
}
if (error) {
return <div>{error.message}</div>;
}
return (
<div>
{todos.map((todo) => (
<div key={todo.id}>{todo.title}</div>
))}
</div>
);
}
export default Second;
'TIL(Today I Learned)' 카테고리의 다른 글
2023.03.13) 항해 36일차 (3-Throttling & Debouncing) (0) | 2023.03.13 |
---|---|
2023.03.13) 항해 36일차 (2-React Query) (0) | 2023.03.13 |
2023.03.11) 항해 34일차 (3- 비동기 통신 - axios) (0) | 2023.03.12 |
2023.03.11) 항해 34일차 (2-json-server, HTTP) (0) | 2023.03.12 |
2023.03.11) 항해 34일차 (1-리덕스 툴킷) (0) | 2023.03.12 |