Redux Thunk的目的就是為了實踐middleware,讓action能做更多的事,想必大家都曾經看過這個錯誤,當action為非同步的時候,就會報錯,Redux Thunk就是為了解決這個問題而生
來看一下Redux Thunk的原始碼
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
你沒看錯,就只有這樣。
原本的action creator 都是回傳action object,在Redux Thunk裡面會檢查若action不是object而是function,就會執行function後再將action往下傳,直到回傳object才會進去reducers
那麼接著來實作一下,情境很簡單,call api拿到資料後dispatch更新資料,讓畫面依據資料長出列表
記得要先安裝
npm install redux-thunk --save
store的部分因為比之前複雜,就從index.js抽離出來獨立,createStore可以接受三個參數(reducer、初始state、applyMiddleware),而applyMiddleware又可以傳入三個參數(thunk、promise、logger
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import reducer from '../reducersBike'
const loggerMiddleware = createLogger()
export default function configureStore(preloadedState) {
return createStore(
reducer,
preloadedState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
)
}
順帶一提,我有另外安裝redux-logger,會在action觸發和state變化時自動印出log,個人覺得在除錯方面還蠻方便的
index.js 將store傳入
import configureStore from './redux/store'
const store = configureStore()
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
action.js
這邊為了簡單做個練習,所以只寫了一個action,處理資料接收,fetchPosts在拿到api資料後dispatch receivePosts(json.data),可以發現fetchPosts是回傳一個函式
getBikeList 可以同時拿到getState和dispatch方法
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(json) {
return {
type: RECEIVE_POSTS,
list: json || []
}
}
function fetchPosts() {
return dispatch => {
return fetch('https://api.kcg.gov.tw/api/service/Get/b4e6ae98-39b7-469b-8c68-56492cad3b71')
.then(res => res.json())
.then(json => dispatch(receivePosts(json.data)))
}
}
export const getBikeList = () => {
return (dispatch, getState) => {
return dispatch(fetchPosts())
}
}
reducersBike.js
經過Middleware處理後,最後將資料傳給reducer,改變state之後重新渲染UI畫面
import { RECEIVE_POSTS } from './action'
const initialState = {
list:[]
}
const bikes = (prevState = initialState, action) => {
switch (action.type) {
case RECEIVE_POSTS:
return {
...prevState,
list:action.list
}
default:
return prevState
}
}
export default bikes
Bike.js
一切準備就緒之後,在componentDidMount階段dispatch getBikeList,在還沒拿到資料等待api回應之前先顯示loading,(本來是要接高雄Youbike據點的api,但api時好時壞,只好改接市場資料orz
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { getBikeList } from './redux/action'
class Bike extends Component {
componentDidMount() {
const { list, getBikeList } = this.props
getBikeList()
}
render() {
return (
<div>
<ul>
{
this.props.list?
this.props.list.map((item)=>{
return (<li>{item.市場地址}</li>)
})
:<p>loading...</p>
}
</ul>
<h1>{console.log(this.props.list)}</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
list: state.list
}
}
const mapDispatchToProps = dispatch => {
return {
getBikeList: () => {
dispatch(getBikeList())
},
}
}
const Bikes = connect(
mapStateToProps,
mapDispatchToProps
)(Bike);
export default Bikes
題外話:api的key居然是中文,第一次看到
最後成功拿到列表!
除了Redux Thunk之外,再處理Middleware非同步這塊,Redux Saga也會是個不錯的選擇,日後有機會再來分析兩者的差異