還記得我們在原本 Redux 環境下是如何處理非同步的事件嗎?
我們透過 redux-thunk 的套件來處理非同步的動作,那麼作為一個官方首推的套件裡,必然也做了相應的處理,這也是為什麼我們這次安裝不需要額外再裝 redux-thunk,因為貼心的套件已經先行載在入了此套件了。
在 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 。