• Home
  • About
  • Posts
  • 1. Redux Toolkit 기본 사용

  • 2. Redux Toolkit 비동기

    • 2.1. 목표
    • 2.2. createAsyncThunk
    • 실전 예제

Redux Toolkit 기본 사용법

📅 2023-05-02
🖋️ Byongho96
  • 1. Redux Toolkit 기본 사용
  • 2. Redux Toolkit 비동기
    • 2.1. 목표
    • 2.2. createAsyncThunk
    • 실전 예제

1. Redux Toolkit 기본 사용

  • Slice 생성 slice는 하나의 데이터에 대한 작은 저장소라고 생각할 수 있다.

    import { createSlice } from '@reduxjs/toolkit'
    
    // useReducer와 비슷하게 초깃갓과, reducer을 전달
    const counterSlice = createSlice({
      name: 'cntSlice',
      initialState: { value: 0 },
      reducers: {
        // 각가의 reducer가 하나의 type에 대응
        up: (state, action) => {
          // 1) 액션 크리에이터를 사용할 경우
          state.value = state.value + action.payload
          // 2) 액션 크리에이터를 사용하지 않을 경우
          // state.value = state.value + action.step
        },
      },
    })
    
    export default counterSlice
    export const { up } = counterSlice.actions // action creator라고 부르며, 이를 통해 컴포넌트 단에서 더 간편한게 action을 생성할 수 있다.
    
  • Store 생성

    redux는 여러 개의 slice를 모아 단 하나의 store에서 관리한다.

    import { configureStore } from '@reduxjs/toolkit'
    import counterSlice from './counterSlice'
    
    const store = configureStore({
      reducer: {
        // 왜 reducer를 넣어주는지는 잘모르겠음..
        counter: counterSlice.reducer, // 왼쪽이름이 나중에 state를 읽을 때 사용된다
      },
    })
    
    export default store
    
  • Provider 생성

    store값을 읽고자 하는 컴포넌트의 최상윗단에 Provider로 공급한다.

    import { Provider } from 'react-redux'
    import store from './store'
    
    export default function App() {
      return (
        <Provider store={store}>
          <Counter />
        </Provider>
      )
    }
    
  • 컴포넌트 사용

    import { useSelector, useDispatch } from 'react-redux'
    import counterSlice, { up } from './counterSlice'
    
    export default function Counter() {
      // store로부터 상태를 읽어온다.
      const count = useSelector((state) => {
        return state.counter.value
      })
    
      // reducer를 발동시킬 dispatch
      const dispatch = useDispatch()
    
      function countUp() {
        // 1. 액션 직접 입력
        // dispatch({ type: 'cntSlice/up', step: 1 }) // type에서 slice의 name을 사용한다.
    
        // 2. 액션 크리에이터 사용
        dispatch(couterSlice.actions.up(2)) // 인자는 자동으로 payload라는 값에 담겨서 감
    
        // 3. 액션 크리에이터 사용
        dispatch(up(2)) // 인자는 자동으로 payload라는 값에 담겨서 감
      }
    
      return (
        <div>
          {count}
          <button onClick={countUp}>+</button>
        </div>
      )
    }
    

2. Redux Toolkit 비동기

리덕스 툴킷 안에 이미 Thunk라는 기능이 내장되어있어 비동기 reducer를 생성할 수 있다.

2.1. 목표

동기적 reducer로도 아래처럼 비동기적인 state업데이트를 구현할 수능 있다.

const status = useSelector((state)=> {
  return state.counter.status
})
const value = useSelector((state)=> {
  return state.counter.value
})

// 비동기 요청의 반환 값을 받은 뒤, state를 동기 reducer로 업데이트한다.
async function fetchValue() {
  const res = await fetch('https://api/example')
  const data = await res.json()
  dispatch(set(data.value))
}

return (
<div>{count} | {status}</div>
<button onClick={fetchValue}>데이터 가져오기</button>
)

하지만 코드의 불필요한 중복을 줄이고 유지 보수를 용이하게 하기 위해서, 아래처럼 비동기 로직을 reducer 자체에 포함시키고자 한다.

const status = useSelector((state)=> {
  return state.counter.status
})
const vlaue = useSelector((state)=> {
  return state.counter.value
})

async function fetchValue() {
  dispatch(asyncFetchReducer()) // 비동기 reducer 호출
}

return (
<div>{count} | {status}</div>
<button onClick={fetchValue}>데이터 가져오기</button>
)

2.2. createAsyncThunk

createAsyncThunk를 통해 비동기 함수를 처리하는 액션을 만들다. action creater**

const asyncUpFetch = createAsyncThunk(
  'counterSlice/asyncUpFetch', // 타입 정의
  async () => {
    const resp = await fetch('https://api/example')
    const data = await resp.json()
    return data.value
  }
)
const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: {
    value: 0,
    status: 'Welcome',
  },
  // createAsyncThunk가 만들어주는 3가지 상태에 대한 리듀서를 정의
  // 실제 reducer 함수는 두번째 인자로 넘겨준다.
  extraReducers: (builder) => {
    builder.addCase(asyncUpFetch.pending, (state, action) => {
      state.status = 'Loading'
    })
    builder.addCase(asyncUpFetch.fullfilled, (state, action) => {
      state.status = action.payload // payload라는 약속된 값으로 위의 코드의 리턴값이 전달됨(data.value)
      state.status = 'complete'
    })
    builder.addCase(asyncUpFetch.pending, (state, action) => {
      state.status = 'fail'
    })
  },
})

동기와 비동기 reducers 동기적인 작업은 reducers -> 툴킷이 액션 크리에이터를 자동으로 만들어줌 비동기적인 작업은 extraReducers -> 툴킷이 액션 크리에이터를 자동으로 만들어주지 못함.

const counterSlice = createSlice({
  name: 'cntSlice',
  initialState: { value: 0, status: 'Welcome' },
  reducers: {
    up: (state, action) => {
      //   state.value = state.value + action.step
      state.value = state.value + action.payload
    },
  },
  extraReducers: (builder) => {
    builder.addCase(asyncUpFetch.fullfilled, (state, action) => {
      state.value = state.value + action.payload // payload는 액션의 결과가 온다
      state.status = 'complete'
    })
  },
})

실전 예제

// counterSlice

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'

const asyncUpFetch = createAsyncThunk('counterSlice/asyncUpFetch', async () => {
  const resp = await fetch('https://api/example')
  const data = await resp.json()
  return data.value
})

const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: {
    value: 0,
    status: 'Welcome',
  },
  reducers: {
    up: (state, action) => {
      state.value = state.value + action.payload
    },
  },
  extraReducers: (builder) => {
    builder.addCase(asyncUpFetch.pending, (state, action) => {
      state.status = 'Loading'
    })
    builder.addCase(asyncUpFetch.fullfilled, (state, action) => {
      state.status = action.payload // payload라는 약속된 값으로 위의 코드의 리턴값이 전달됨(data.value)
      state.status = 'complete'
    })
    builder.addCase(asyncUpFetch.pending, (state, action) => {
      state.status = 'fail'
    })
  },
})
// Counter.js
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { up, asyncUpFetch } from './counterSlice'

function Counter() {
  const dispatch = useDisptch()
  const count = useSelector((state) => {
    return state.counter.value
  })
  const status = useSelector((state) => {
    return state.counter.status
  })

  return (
    <div>
      <div>
        {' '}
        {count} | {status}{' '}
      </div>
      <button onClick={() => dispatch(up(2))}>count up</button>
      <button onClick={() => dispatch(asyncUpFetch(2))}>fetch count</button>
    </div>
  )
}
이전 포스트

React Context API 란?

다음 포스트

React & Webpack 이미지 로딩 최적화

작성자 프로필
전체 글 (127)
  • Animation
    • Backend
      • Django
      • Spring
    • DevOps
      • AWS
      • CI&CD
      • Docker
      • Git
      • Gunicorn
      • Kubernetes
      • Nginx
    • Frontend
      • Gatsby
      • React
      • Vue
    • Knowledge
      • .etc
      • Algorithm
      • Data Structure
      • Database
      • Design Pattern
      • Interview
      • Network
      • Web
    • Language
      • CSS
      • HTML
      • Java
      • JavaScript
      • Linux
      • Python

    Copyright © 2023 Byongho96  & Powered by Gatsby