很多时候,我们的state必须要透过HTTP Request从後端取得。然而发送Request常用的fetch或是axios是非同步的。虽然我们可以透过以下方式把资料送进去Redux:
fetch( "URL", {
method: "GET"
})
.then(res => res.json())
.then(data => {
dispatch({type:"TYPE", payload: {data}});
})
.catch(e => {
/*发生错误时要做的事情*/
}
)
但最理想的状况还是让这个fetch的过程被模组、抽象化,也就是不应该还要让UI绘制程序还要自己去call fectch API。我们希望UI绘制程序只需要呼叫一个函式,从fetch到更新Redux的这串过程都会完成。
不论是在Flux,还是传统的MVC、MVP、MVVM观念下,都希望把资料处理的程序抽离UI绘制的程序,而不是让两者混杂在一起
讲白一点,我们的流程本来是:
现在我们希望流程改成这样:
一般会把2这种在本来行为之间(1和3)的加工过程称为middleware(中介层)。
Redux-Thunk就是一个简化Redux处理非同步事件的中介层套件。它的运作流程是这样的,基本上就跟我们刚刚说的差不多:
上图来源。
接下来我们会实际操作一次Redux-Thunk,试着把MenuItem的资料改成从後端取得。资料会用我放在自己github的台湾的县市列表JSON档。
{
"cityList":[
"台北市",
"基隆市",
"新北市",
(略......)
]
}
请打开terminal,输入:
npm install redux-thunk --save
一般会在这里以变数统一管理action字串。不过这里我们先拿来放等等要定义的fetch
在src/model/action.js中,定义一个函式,把item改成fetch函式得到的资料。Redux-Thunk会把dispatch函式当成函式的参数传入。我们则要在非同步事件结束後再次呼叫dispatch,给予对应的action和payload。
因为现在我们的reducer还没有这种一次修改所有资料的action,我们先加一个SET_ITEM
,等等再加回reducer中。
export const fetchCityItem = () => {
return (dispatch) => {
fetch( "https://raw.githubusercontent.com/JiaAnTW/mask/master/dist.json", {
method: "GET"
})
.then(res => res.json())
.then(data => {
dispatch({
type: "SET_ITEM",
payload: {itemNewArr: data["cityList"]}
});
})
.catch(e => {
console.log(e);
})
};
};
Redux提供了applyMiddleware
这个函式来让我们安装middleware到Redux中。用法是将applyMiddleware(中介层1,中介层2,...)
放在createStore的第二个参数中。
现在,请引入Redux-Thunk的thunk
和Redux的applyMiddleware
,并加入我们的store中:
import {createStore, applyMiddleware } from "redux";
import {itemReducer} from "./reducer.js";
import thunk from "redux-thunk";
const itemStore = createStore(itemReducer,applyMiddleware(thunk));
export {itemStore};
这样使用Redux-thunk的架构就完成了。
const initState = {
menuItemData: [
"Like的发问",
"Like的回答",
"Like的文章",
"Like的留言"
],
};
const itemReducer = (state = initState, action) => {
switch (action.type) {
case 'ADD_ITEM': {
const menuItemCopy = state.menuItemData.slice();
return { menuItemData: [action.payload.itemNew].concat(menuItemCopy) };
}
case 'SET_ITEM': {
return { menuItemData: action.payload.itemNewArr };
}
case 'CLEAN_ITEM': {
return { menuItemData: [] };
}
default:
return state;
}
};
export {itemReducer};
触发Redux-Thunk的方式,是在需要的地方呼叫
dispatch( 刚刚定义的非同步函式() );
也就是你可以在src/page/MenuPage新增一个按钮:
<button onClick={()=>{
dispatch(
fetchCityItem()
);
}}>抓取并修改menuItem</button>
按下去之後,Redux就会根据我们刚刚定义的内容,先执行发送Http Request,等资料回来,才执行dispatch,把action和刚刚放入payload的县市资料丢到reducer去更新。
import React, { useState, useReducer,useMemo,useEffect} from 'react';
import useMouseY from '../util/useMouseY';
import MenuItem from '../component/MenuItem';
import Menu from '../component/Menu';
import { OpenContext } from '../context/ControlContext';
import { useSelector, useDispatch } from 'react-redux';
import { fetchCityItem } from '../model/action';
const reducer = function(state, action){
switch(action.type){
case "SWITCH":
return !state;
default:
throw new Error("Unknown action");
}
}
const MenuPage = () =>{
const [isOpen, isOpenDispatch] = useReducer(reducer,true);
const menuItemData = useSelector(state => state.menuItemData);
const dispatch = useDispatch();
let menuItemArr = useMemo(()=>menuItemData.map((wording) => <MenuItem text={wording} key={wording}/>),[menuItemData]);
return (
<OpenContext.Provider value={{
openContext: isOpen,
setOpenContext: isOpenDispatch
}} >
<Menu title={"Andy Chang的like"}>
{menuItemArr}
</Menu>
<button onClick={()=>{
dispatch({
type: "ADD_ITEM",
payload: {itemNew:"测试资料"}
});
}}>更改第一个menuItem</button>
<button onClick={()=>{
dispatch(
fetchCityItem()
);
}}>抓取并修改menuItem</button>
</OpenContext.Provider>
);
}
export default MenuPage;
Scope Scope 指得是Coroutines 可以作用的范围。 在Main thread上或I...
今天是我最後一次以git的主题跟各位沟通啦~ 说实话,觉得要坚持30天,天天发文真的有点难馁XD ...
大家好!虽然今天是 Day 2,不过严格来说是系列文的第一天。今天要来谈谈「为什麽我们需要在前端做...
大家好,大家都叫我西瓜。因为想转职写游戏,而游戏中会让人第一个想到、也是能在第一瞬间吸引人的就是画面...
工程师太师了: 第7话 杂记: 这次的COVID-19 疫情, 究竟对我们产生了什麽影响? 很多人会...