iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
自我挑戰組

30天深入淺出Redux系列 第 17

Redux 深入淺出 - [ Day 17 ] Redux Toolkit asyncThunk 非同步範例

  • 分享至 

  • xImage
  •  

還記得我們在原本 Redux 環境下是如何處理非同步的事件嗎?

我們透過 redux-thunk 的套件來處理非同步的動作,那麼作為一個官方首推的套件裡,必然也做了相應的處理,這也是為什麼我們這次安裝不需要額外再裝 redux-thunk,因為貼心的套件已經先行載在入了此套件了。

https://ithelp.ithome.com.tw/upload/images/20220926/20129020WKXWIEdE0u.png

在 Redux Toolkit 處理非同步事件時需要用 createAsyncThunk 的 function 來做處理,他的原理一樣是透過 redux-thunk 的方式來實現非同步的處理,但現在 slice 的寫法已經不用額外去設定 action type 了,所以會比原來的方式單純乾淨許多。—官網連結

說了這麼多不如實際做一遍吧!我們拿我們之前的 pokemon 來當範例,一樣先想辦法移植過來:

// features/slices/pokemonSlice.js
const { createSlice } = require("@reduxjs/toolkit");

// from only pokemon
const initialState = {
  loading: false,
  data: [],
  error: ''
}

const pokemonSlice = createSlice({
  name: 'pokemon',
  initialState,
  reducers: {}
})

module.exports = pokemonSlice.reducer;

接著,不要忘記要去調整 store.js:

// features/store.js
const configureStore = require('@reduxjs/toolkit').configureStore;
const coffeeReducer = require('./slices/coffeeSlice');
const coffeeBeanReducer = require('./slices/coffeeBeanSlice');
const cakeReducer = require('./slices/cakeSlice');
const assetsReducer = require('./slices/assetsSlice');
const pokemonRedcer = require('./slices/pokemonSlice');

const store = configureStore({
  reducer: {
    coffee: coffeeReducer,
    coffeeBean: coffeeBeanReducer,
    cake: cakeReducer,
    assets: assetsReducer,
    pokemon: pokemonRedcer
  }
})

module.exports = { store }

那麼我們開始來使用 createAsyncThunk 整合之前拿取 pokemon 資料的 function 吧!

讓我們先回到 pokemonSlice 裏面並引入 createAsyncThunk & axios,如下:

// features/slices/pokemonSlice.js
const { createSlice, createAsyncThunk } = require("@reduxjs/toolkit");
const axios = require('axios');

// from only pokemon
const initialState = {
  loading: false,
  data: [],
  error: ''
}

// 這裡我們照著 only/action/pokemon.js 裏面的邏輯重寫,使用 createAsyncThunk 就是這麼簡單
// 我們不需要去管後續的 state change function,因為可以透過 extraReducer 來補足
// 這裡要注意在 createAsyncThunk 第一個參數的 name 一定要對好對應的 slice name
const fetchPokes = createAsyncThunk('pokemon/fetchPokes', (url) => {
  return axios
    .get(url)
    .then(response => response.data)
})

const pokemonSlice = createSlice({
  name: 'pokemon',
  initialState,
  reducers: {},
  // 這裡就按常規套路走,在 createAsyncThunk 之後可以接的有 pending, fulfilled, rejected 三種情況
  extraReducers: builder => {
    builder.addCase(fetchPokes.pending, state => {
      state.loading = true
      return state;
    })
    builder.addCase(fetchPokes.fulfilled, (state, action) => {
      state.loading = false
      state.data = action.payload
      state.error = ''
      return state;
    })
    builder.addCase(fetchPokes.rejected, (state, action) => {
      state.loading = false
      state.data = []
      state.error = action.error.message
      return state
    })
  }
})

module.exports = pokemonSlice.reducer;
module.exports.fetchPokes = fetchPokes;

完成以上調整後,我們回到 ./index.js 裏面來測試看看是否有確實拿到資料,將用不到的指令先碼掉,並引入剛才定義好的 fetchPokes,並帶入和之前相同的 url,如下:

// ./index.js
const { store } = require('./features/store');
const coffeeActions = require('./features/slices/coffeeSlice').coffeeActions;
const coffeeBeanActions = require('./features/slices/coffeeBeanSlice').coffeeBeanActions;
const cakeActions = require('./features/slices/cakeSlice').cakeActions;
const fetchPokes = require('./features/slices/pokemonSlice').fetchPokes;

console.log('Initial State', store.getState());
const unsubscribe = store.subscribe(() => console.log('更新', store.getState()));

// store.dispatch(coffeeActions.coffeeOrdered({qty: 1, money: 10}));
// store.dispatch(coffeeActions.coffeeOrdered({qty: 4, money: 40}));
// store.dispatch(coffeeActions.coffeeRestocked({qty: 10, money: 20}));
// store.dispatch(coffeeBeanActions.coffeeBeanOrdered({qty: 2, money: 10}));
// store.dispatch(coffeeBeanActions.coffeeBeanRestocked({qty: 5, money: 5}));
// store.dispatch(cakeActions.cakeOrdered({qty: 3, money: 45}));
// store.dispatch(cakeActions.cakeRestocked({qty: 6, money: 30}));
store.dispatch(fetchPokes(`https://pokeapi.co/api/v2/pokemon?offset=0&limit=20`))

// unsubscribe();

於終端機輸入:

node index.js

應該會看到以下訊息:

Initial State {
  coffee: { numOfCoffee: 20 },
  coffeeBean: { numOfCoffeeBean: 20 },
  cake: { numOfCake: 20 },
  assets: { money: 1000 },
  pokemon: { loading: false, data: [], error: '' }
}
更新 {
  coffee: { numOfCoffee: 20 },
  coffeeBean: { numOfCoffeeBean: 20 },
  cake: { numOfCake: 20 },
  assets: { money: 1000 },
  pokemon: { loading: true, data: [], error: '' }
}
更新 {
  coffee: { numOfCoffee: 20 },
  coffeeBean: { numOfCoffeeBean: 20 },
  cake: { numOfCake: 20 },
  assets: { money: 1000 },
  pokemon: {
    loading: false,
    data: {
      count: 1154,
      next: 'https://pokeapi.co/api/v2/pokemon?offset=20&limit=20',
      previous: null,
      results: [Array]
    },
    error: ''
  }
}

至此,我們成功地完成 redux toolkit 非同步的處理,下一篇我們將這個樣子的程序移入 React 的框架中,讓他成為我們常見的 React Redux 配置。

這裡附上這個章節的 source code


上一篇
Redux 深入淺出 - [ Day 16 ] Redux Toolkit ExtraReducer 整理蛋糕
下一篇
Redux 深入淺出 - [ Day 18 ] React Redux 簡介
系列文
30天深入淺出Redux31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言