https://light9639.github.io/Redux-Toolkit-AsyncThunk/
โจ Redux-Toolkit์ AsyncThunk๋ฅผ ์ด์ฉํ ์ซ์ ์ฆ๊ฐ ์์ ์ ๋๋ค. โจ
- React ์์ฑ
npm create-react-app my-app
# or
yarn create react-app my-app
- vite๋ฅผ ์ด์ฉํ์ฌ ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ ค๋ฉด
npm create vite@latest
# or
yarn create vite
- ํฐ๋ฏธ๋์์ ์คํ ํ ํ๋ก์ ํธ ์ด๋ฆ ๋ง๋ ํ React ์ ํ, Typescirpt ์ ํํ๋ฉด ์์ฑ ์๋ฃ.
- Redux-Toolkit ์ค์น ๋ช ๋ น์ด
npm install redux react-redux @reduxjs/toolkit
# or
yarn add redux react-redux @reduxjs/toolkit
react-redux
์์Provider
ํจ์ ๊ฐ์ ธ์จ ํstore
ํ์ผ์import
ํ ํ์<Provider store={store}></Provider>
์ผ๋ก<App />
์ ๋๋ฌ์ธ๋ฉดRedux-Toolkit
์ฌ์ฉ์ค๋น ์๋ฃ.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { Provider } from 'react-redux'
import store from './redux/store'
import "./index.css";
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
)
Counter
์ปดํฌ๋ํธ๋ฅผimport
ํ ํ ์ฌ์ฉํ๋ค.
import React from './assets/react.svg'
import Counter from "./components/Counter";
import './App.css'
export default function App(): JSX.Element {
return (
<div className="App">
<div>
<a href="https://reactjs.org" target="_blank">
<img src={React} className="logo react" alt="React logo" />
</a>
<a href="https://ko.redux.js.org/introduction/getting-started/" target="_blank">
<img src="https://camo.githubusercontent.com/7b7f04b16cc2d2d4a32985710e4d640985337a32bbb1e60cdacede2c8a4ae57b/68747470733a2f2f63646e2e776f726c64766563746f726c6f676f2e636f6d2f6c6f676f732f72656475782e737667" className="logo" alt="Redux logo" />
</a>
</div>
<h1>React + Redux</h1>
<Counter></Counter>
<div className="card">
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
<p className="read-the-docs">
Click on the React and Redux logos to learn more
</p>
</div>
</div>
)
}
createAsyncThunk
๋ฅผfetch
์๋ฐ์คํฌ๋ฆฝํธ ํจ์๋ฅผ ์ฌ์ฉํ์ฌdata
๊ฐ์return
์ํจ๋ค.
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
// ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
export const asyncUpFetch = createAsyncThunk(
'counterSlice/asyncUpFetch',
async () => {
const resp = await fetch('https://api.countapi.xyz/hit/opesaljkdfslkjfsadf.com/visits')
const data = await resp.json();
return data.value;
}
)
// ์ด๊ธฐ ๊ฐ์ ํ์
์ ์
interface initialType {
value: number;
status: string;
}
// ์ด๊ธฐ ๊ฐ ์ ์ธ
const initialState: initialType = {
value: 0,
status: 'Welcome'
}
// slice ์์ฑ
const counterSlice = createSlice({
// slice ์ด๋ฆ ์ ์
name: 'counterSlice',
// ์ด๊ธฐ ๊ฐ
initialState,
// ๋ฆฌ๋์
reducers: {
up: (state, action: PayloadAction<number>) => {
state.value = state.value + action.payload;
},
set: (state, action: PayloadAction<number>) => {
state.value = action.payload
}
},
// ๋น๋๊ธฐ ํต์ ์ก์
์ ๋ํ reducer
extraReducers: (builder) => {
// ํต์ ์ค
builder.addCase(asyncUpFetch.pending, (state, action) => {
state.status = 'Loading';
})
// ํต์ ์ฑ๊ณต
builder.addCase(asyncUpFetch.fulfilled, (state, action) => {
state.value = action.payload;
state.status = 'complete';
})
// ํต์ ์๋ฌ
builder.addCase(asyncUpFetch.rejected, (state, action) => {
state.status = 'fail';
})
}
});
// slice ๋ด๋ณด๋ด๊ธฐ ์ฌ๊ธฐ์๋ reducer๊ฐ ์๊ธฐ ๋๋ฌธ์ counterSlice๋ก ๋ด๋ณด๋ธ๋ค
export default counterSlice;
// ํจ์ ๋ด๋ณด๋ด๊ธฐ
export const { up, set } = counterSlice.actions;
RootState
๋useSelector
์ฌ์ฉ์ ํ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํด์ ์์ฑ.AppDispatch
๋useDispatch
๋ฅผ ์ข ๋ ๋ช ํํ๊ฒ ์ฌ์ฉํ๊ธฐ ์ํด์ ์์ฑ.
import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './counterSlice';
// ์คํ ์ด ์์ฑ
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// ์คํ ์ด ๋ด๋ณด๋ด๊ธฐ
export default store;
useDispatch
์useSelector
์typed versions
๋ฅผ ๋ง๋๋ ๊ฒ์ด ์ฌ์ฉํ๋๋ฐ ๋ ์ข๋ค.useSelector
์์state
์ธ์์RootState
๋ฅผimport
ํด์ ํ์ ์ผ๋ก ๋ฃ์ด์คฌ์๋๋ฐ, ์ด๊ฑธ ๋ฐ๋ณตํ๋ ๊ฒ์์ ๋ฒ์ด๋ ์ ์๋ค.useDispatch
์์ ๊ธฐ๋ณธ์ ์ธDispatch
ํ์ ์thunks
์ ๋ํ์ฌ ์์ง ๋ชปํ๊ธฐ์thunk middleware
ํ์ ์ ํฌํจํ๊ณ ์๋AppDispatch
๋ผ๋ ์ปค์คํ ํ์ ์useDispatch
์ ์ฌ์ฉํ ํ์๊ฐ ์๋ค. ๋ฐ๋ผ์ ์ฌ์ ์useDispatch
์AppDispatch
๋ฅผ ์ ์ฉ์์ผ๋์ผ๋ฉด ์ฌ์ฉํ ๋๋ง๋คAppDispatch
๋ฅผimport
ํ์ง ์์๋ ๋๋ค.
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "../redux/store";
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
useTypedSelector.ts
์์useAppDispatch
,useAppSelector
๋ฅผimport
ํ ํ ์ฌ์ฉํ๋ฉด ๋๋ค.counterSlice
์์asyncUpFetch
,up
,set
ํจ์import
ํ ์ฌ์ฉ.
import React from 'react'
import { useAppDispatch, useAppSelector } from '../hooks/useTypedSelector';
import { asyncUpFetch, up, set } from '../redux/counterSlice';
export default function Counter(): JSX.Element {
const dispatch = useAppDispatch();
const count = useAppSelector((state) => {
return state.counter.value;
});
const status = useAppSelector((state) => {
return state.counter.status;
});
return (
<div>
<button onClick={() => {
dispatch(up(2));
}}>+2</button>
<button onClick={() => {
dispatch(set(0));
}}>Reset</button>
{/* ๋งํฌ๋ฅผ ์กฐํํ ๋๋ง๋ค ์ซ์๊ฐ ํ๋์ฉ ๋์ด๋์, ์ซ์๊ฐ 1์ฉ ๋์ด๋จ */}
<button onClick={() => {
dispatch(asyncUpFetch());
}}>+ async fetch</button>
<br />
<div className='status'>{count} | {status}</div>
</div>
)
}
- ์ถ์ฒ 1 : Redux-Toolkit ํํ์ด์ง
- ์ถ์ฒ 2 : Velog ๊ธ