Redux-toolkit에서 redux-thunk / redux-saga를 사용해서 비동기 작업 처리하기 🤓
promise
를 반환한다. -> 디버깅이 어려움syntax
-> 간단한 프로젝트, 비기너에 적합Redux-toolkit에는 기본적으로 thunk가 내장되어있어서, 별도의 설치없이 사용할 수 있다.
Codesandbox 참고
store.js
// redux/store.js
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";
export default configureStore({
reducer: {
users: userReducer
}
});
index.js
Provider
로 전체 컴포넌트 감싸기
// index.js
import { Provider } from "react-redux";
import store from "./redux/store";
// omit other codes...
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
userSlice.js
Slice 생성
// redux/userSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
export const fetchUser = createAsyncThunk("users/fetchUser", async () => {
return axios
.get("url")
.then((res) => res.data)
.catch((error) => error);
});
const usersSlice = createSlice({
name: "users",
initialState: {
users: [],
loading: false,
error: ""
},
reducers: {},
extraReducers: {
[fetchUser.pending]: (state) => {
state.loading = true;
state.users = [];
state.error = "";
},
[fetchUser.fulfilled]: (state, action) => {
state.users = action.payload;
state.loading = false;
state.error = "";
},
[fetchUser.rejected]: (state, action) => {
state.loading = false;
state.users = [];
state.error = action.payload;
}
}
});
export default usersSlice.reducer;
컴포넌트에서 사용하기
import { useSelector, useDispatch } from "react-redux";
import { fetchUser } from "./redux/userSlice";
export default function App() {
const dispatch = useDispatch();
const { users, loading, error } = useSelector((state) => state.users);
if (error) {
return <p>Something went wrong! please, try again.</p>;
}
if (loading) {
return <p>Loading</p>;
}
return (
<div className="App">
<h1>Fetch user data</h1>
<button onClick={() => dispatch(fetchUser())}>Get users</button>
{users?.length > 0 &&
users.map((user) => <div key={user.id}>{user.name}</div>)}
</div>
);
}
a saga is like a separate thread in your application that's solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.
ES6의 Generator function
사용
async/await
처럼) 구현callback hell
을 피할 수 있음action
에 대해서 세부적인 컨트롤이 가능함Thunk말고 다른 미들웨어를 사용할 경우, 이전(기존의 redux)과 비슷하게 적용하면 된다.
saga
: sideEffect를 가진 Generator Function
fork
: 동기적으로 함수 호출call
: 비동기적으로 함수 호출 (saga
안에서 sideEffect를 가진 함수를 직접 호출X)put
: Dispatchcall
과 put
을 사용해서, sideEffect
를 가진 로직을 완전히 분리한다. 따라서 디버깅과 테스팅이 수월하다.
Codesandbox 참고
store.js
// redux/store.js
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";
import userReducer from "./userSlice";
import { watcherSaga } from "./sagas/rootSaga";
const reducer = combineReducers({
users: userReducer
// others...
});
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: reducer,
middleware: [sagaMiddleware]
});
sagaMiddleware.run(watcherSaga); // Listener
export default store;
index.js
Provider
로 전체 컴포넌트 감싸기
// index.js
import store from "./redux/store";
import { Provider } from "react-redux";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
rootSaga.js
rootSaga
를 생성해서 여러개의 saga
를 합친다.
아래의 경우, 컴포넌트에서 getUser.type
을 dispatch
하면 handleGetUser를 실행한다.
// redux/sagas/rootSaga.js
import { takeLatest } from "redux-saga/effects";
import { handleGetUser } from "../sagas/userSaga";
import { getUser } from "../userSlice";
export function* watcherSaga() {
yield takeLatest(getUser.type, handleGetUser);
}
만약 여러개의 saga
를 watching
하고 싶다면, 아래와 같이 작성한다.
export function* watcherSaga() {
yield [
takeLatest(getUser.type, handleGetUser)
takeLatest(something.type, logout)
]
}
userSaga.js
User의 데이터를 fetch하는 로직을 다루는 saga
생성하기
// redux/sagas/userSaga.js
import { call, put } from "redux-saga/effects";
import { setUser, failedGetUser } from "../userSlice";
import { fetchUser } from "../../api/api";
export function* handleGetUser() {
try {
const res = yield call(fetchUser);
yield put(setUser(res));
} catch (error) {
yield put(failedGetUser(error));
}
}
api.js
Fetch 로직 작성
// api/api.js
import axios from "axios";
export const fetchUser = async () => {
return axios
.get("https://jsonplaceholder.typicode.com/users")
.then((res) => res.data)
.catch((error) => error);
};
userSlice.js
// redux/userSlice.js
import { createSlice } from "@reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: {
users: [],
loading: false,
error: ""
},
reducers: {
getUser(state) {
state.loading = true;
},
setUser(state, action) {
state.users = action.payload;
state.loading = false;
},
failedGetUser() {
state.error = action.payload;
state.loading = false;
}
}
});
export const { getUser, setUser, failedGetUser } = userSlice.actions;
export default userSlice.reducer;
안녕하세요 글 잘 봤습니다
다름이 아니고 질문이 있습니다
export const fetchUser = async () => {
return axios
.get("https://jsonplaceholder.typicode.com/users")
.then((res) => res.data)
.catch((error) => error);
};
async 예약어 쓰시고 await 가 아닌 이유가 따로 있으신가요?