Skip to content

๐Ÿงช Redux-Toolkit์˜ AsyncThunk๋ฅผ ์ด์šฉํ•œ ์ˆซ์ž ์ฆ๊ฐ€ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

Notifications You must be signed in to change notification settings

light9639/Redux-Toolkit-AsyncThunk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

8 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿงช Redux-Toolkit์˜ AsyncThunk๋ฅผ ์ด์šฉํ•œ ์ˆซ์ž ์ฆ๊ฐ€ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

:octocat: https://light9639.github.io/Redux-Toolkit-AsyncThunk/

light9639 github io_Redux-Toolkit-AsyncThunk_

โœจ Redux-Toolkit์˜ AsyncThunk๋ฅผ ์ด์šฉํ•œ ์ˆซ์ž ์ฆ๊ฐ€ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. โœจ

๐ŸŽ‰ React ์ƒ์„ฑ

  • 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 ์„ค์น˜

  • Redux-Toolkit ์„ค์น˜ ๋ช…๋ น์–ด
npm install redux react-redux @reduxjs/toolkit
# or
yarn add redux react-redux @reduxjs/toolkit

โœ’๏ธ main.tsx, App.tsx ์ˆ˜์ • ๋ฐ ์ž‘์„ฑ

โšก main.tsx

  • 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>,
)

โšก App.tsx

  • 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>
  )
}

โœ’๏ธ counterSlice.ts, store.ts, useTypedSelector.ts, Counter.tsx ์ˆ˜์ • ๋ฐ ์ž‘์„ฑ

โšก counterSlice.ts

  • 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;

โšก store.ts

  • 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;

โšก useTypedSelector.ts

  • 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;

โšก Counter.tsx

  • 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>
    )
}

๐Ÿ“Ž ์ถœ์ฒ˜

About

๐Ÿงช Redux-Toolkit์˜ AsyncThunk๋ฅผ ์ด์šฉํ•œ ์ˆซ์ž ์ฆ๊ฐ€ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published