1. instance 생성
/.axios/api.js 에서 api 가공하기
import axios from "axios";
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
});
export default instance;
이후 axios 를 사용하려면 Import 해와서
import api from "./axios/api";
const { data } = await api.get("/todos");
axios => api로 변경해주면 된다. 그리고 baseURL을 지정해놨기 때문에 /todos 까지만 작성해도 된다.
2. interceptor 생성
import axios from "axios";
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
});
//req와 res 사이의 작업
//인자로 항상 콜백함수 2개가 들어감
instance.interceptors.request.use(
//요청을 보내기 전 수행되는 함수
function (config) {
console.log("인터셉터 요청 성공!");
return config;
},
//오류 요청을 보내기 전 수행되는 함수
function (error) {
console.log("인터셉터 요청 오류!");
return Promise.reject(error); //오류 처리
},
);
instance.interceptors.response.use(
//응답을 내보내기 전 수행되는 함수
function (response) {
console.log("인터셉터 응답 받았습니다!");
return response;
},
//오류 응답을 내보내기 전 수행되는 함수
function (error) {
console.log("인터셉터 응답 오류 발생!");
return Promise.reject(error);
},
);
export default instance;
//fetchTodos 의 get 요청이 실행되면서 axios를 콜하게되고, 인터셉터 요청과 응답이 실행됨.
timeout 속성을 이용해 1초가 지나면 error로 간주할 수 있게 설정해보자
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
timeout: 1, // 0.001초가 지나면 error로 간주함
});
3. middleware (redux-thunk)
그 전에 기본적인 카운터 앱의 리덕스 툴킷을 사용한 구조를 살펴보자
index.jsx
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>,
);
config/configStore.jsx
import counter from "../modules/counter";
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: { counter },
});
export default store;
modules/counter.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
number: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
plusN: (state, action) => {
state.number = state.number + action.payload;
},
minusN: (state, action) => {
state.number = state.number - action.payload;
},
},
});
export default counterSlice.reducer;
export const { plusN, minusN } = counterSlice.actions;
App.jsx
function App() {
const [num, setNum] = useState(0);
const globalNumber = useSelector((state) => state.counter.number);
const dispatch = useDispatch();
return (
<div className="App">
<button onClick={() => dispatch(plusN(+num))}>+</button>
<button onClick={() => dispatch(minusN(+num))}>-</button>
<input
type="number"
value={num}
onChange={(e) => setNum(+e.target.value)}
/>
현재 카운트: {globalNumber}
</div>
);
}
Thunk 사용하기
1. redux toolkit 내장 api인 createAsyncThunk 함수를 통해 thunk 함수 만들기
2. createSlice 안의 extraReducer를 수정해줌으로서 Thunk를 등록한다.
3. dispatch 하기
Thunk함수를 만들고
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
//thunk함수 만들기
//2개의 INPUT (이름, 함수)
export const __addNumber = createAsyncThunk(
"ADD_NUMBER_WAIT",
(payload, thunkAPI) => {
//수행하고 싶은 동작 : 3초 기다리기
setTimeout(() => {
thunkAPI.dispatch(plusN(payload)); //component에서 넘겨받은 payload를 전달
}, 3000);
},
);
export const __minusNumber = createAsyncThunk(
"ADD_NUMBER_WAIT",
(payload, thunkAPI) => {
//수행하고 싶은 동작 : 3초 기다리기
setTimeout(() => {
thunkAPI.dispatch(minusN(payload)); //component에서 넘겨받은 payload를 전달
}, 3000);
},
);
const initialState = {
number: 0,
};
//...
이름 input은 의미가 없으니 컨벤션을 따라 적당히 지어준다
App.js에서 dispatch 보내기 (thunk함수에!)
const onClickAddNumberHandler = () => {
dispatch(__addNumber(+num));
};
const onClickMinusNumberHandler = () => {
dispatch(__minusNumber(+num));
};
리덕스 미들웨어를 사용하면 액션이 리듀서로 전달되기 전에 중간에 어떤 작업을 더 할 수 있다. ( = 함수를 넣을 수 있다)
즉 객체가 아닌 함수를 dispatch할 수 있게 해준다. <- thunk의 핵심이다.
4. Thunk 와 API 사용하기
아래는 module/todosSlice.js 기본 구조이다. 이제 여기에 thunk 함수를 추가하고, todosSlice의 createSlice안의 extrareducers에 전달해주면 미들웨어가 적용된다.
// src/redux/modules/todosSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
//초깃값
const initialState = {};
//thunk 함수
// 액션생성 함수 + 리듀서
export const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {},
extraReducers: {},
});
export const {} = todosSlice.actions;
export default todosSlice.reducer;
즉
1. thunk 함수 구현
2. 리듀서 로직 구현 (createSlice의 reducers 에서 => extraReducer로!)
- 진행에 관련된 state 관리할 수 있음 (loading, isError, error 등) + 물론 data도.
- root reducer에는 추가되어있어야 함
3. 기능 확인 (network)
4. 화면 렌더링
이 순서로 하면 된다.
// src/redux/modules/todosSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
//초깃값
const initialState = {
todos: [],
error: "",
isLoading: false,
isError: false,
};
//thunk 함수 (비동기 함수 처리 -> createAsyncThunk)
export const __getTodos = createAsyncThunk(
"getTodos",
async (payload, thunkAPI) => {
try {
const response = await axios.get("http://localhost:4000/todos");
return thunkAPI.fulfillWithValue(response.data);
} catch (e) {
return thunkAPI.fulfillWithValue(e);
}
},
);
// 액션생성 함수 + 리듀서
export const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {},
extraReducers: {
[__getTodos.pending]: (state) => {
state.isLoading = true;
},
[__getTodos.fulfilled]: (state, action) => {
state.todos = action.payload;
state.isLoading = false;
},
[__getTodos.rejected]: (state, action) => {
state.isError = true;
state.error = action.payload;
},
},
});
export const {} = todosSlice.actions;
export default todosSlice.reducer;
//configStore.js
import { configureStore } from "@reduxjs/toolkit";
import todos from "../modules/todosSlice";
const store = configureStore({
reducer: {
todos,
},
});
export default store;
//App.js
import React from "react";
import { __getTodos } from "./redux/modules/todosSlice";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
const App = () => {
const dispatch = useDispatch();
const { isLoading, error, todos } = useSelector((state) => {
return state.todos;
});
//side effect -> mount 될 때 처리
useEffect(() => {
dispatch(__getTodos());
}, [dispatch]);
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
return (
<>
<h1>
{todos.map((todo) => (
<div key={todo.id}>{todo.title}</div>
))}
</h1>
</>
);
};
export default App;
'Computer Programming > React' 카테고리의 다른 글
카카오 개발자들의 React Query 선택 이유 | react-query 사용법 (0) | 2023.07.06 |
---|---|
리액트가 Virtual DOM을 사용하는 이유? (1) | 2023.07.05 |
React 기능 구현 스터디 - 2.검색 기능 (리팩토링 회고) ☀️ (0) | 2023.07.03 |
Redux Toolkit 으로 카운터 | 투두 리팩토링 ☀️ (0) | 2023.06.30 |
Storybook 스토리북이란? (0) | 2023.06.29 |