当专案中的阶层变复杂,state和props变的很多,资料在多层component之间的传递也越来越多。产生了一堆纯粹用来传递用的props和父component。
工程师心想: 「有没有一个全局的state和setState可以让所有的元件共同操作呢?」
於是Global State的概念就诞生了。
Global State的概念就像是住宅大厦的公共设施,它不单独属於任何一个人,也能够被任何人取用。
在【Day.17】React入门 - 利用useContext进行多层component沟通中,我们提及了React本身提供的状态管理API。但是我们也在後续的文章中讲到了Context API本身并不是设计拿来做大型专案的状态管理,因而存在麻烦的效能问题。
现在,就让我们来认识最被广为使用的状态管理套件 - Redux。
Redux在2015年诞生。他不只是一个普通的全局state和setState工具而已,Redux受到了Facebook提出的设计概念Flux启发。有关Flux诞生的原因,我们在【Day.27】React进阶 - 用useReducer定义state的更动原则 有说明。
总之,我们不应该让别人能够随意修改state,而是要预先定义好修改的规则,并让其他开发者透过这些规则来操作。
同时,我们也说过在Flux观念下,我们操作state的过程大概变成像这样:
Redux基於上述Flux的架构外,又做了一些补充和修改
而操作者可以有两种操作选择:
依据上述流程,我们就能在任何地方取得state,同时state也会透过管理者规定好的方式更新。
上图引用自Redux官方文件
首先,请先打开terminal,输入
npm install --save redux react-redux
Redux和专为React打造的react-redux就会被安装。
请在src底下新增model资料夹,并在里面建立reducer.js
和useReducer
一样,当操作者呼叫dispatch後,Redux会呼叫Reducer函式。Reducer函式的语法是:
请注意第一次执行的时候,reducer的第一个参数(state)如果有给default value,该default value就会变成state的初始值。
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 'CLEAN_ITEM': {
return { menuItemData: [] };
}
default:
return state;
}
};
export {itemReducer};
而虽然我们这里是直接把action字串定义在reducer中,但比较好的方式应该是让action字串也用变数来管理,并用该变数来判断action:
const ADD_ITEM = 'ADD_ITEM';
const CLEAN_ITEM = 'CLEAN_ITEM';
在src/model底下新增store.js
要创立store的话,必须要使用redux提供的APIcreateStore
import {createStore} from "redux";
接着引入刚刚定义好的reducer,丢给createStore就能产生store了
import {createStore} from "redux";
import {itemReducer} from "./reducer.js";
const itemStore = createStore(itemReducer);
export {itemStore};
createStore
还可以吃一些middleware参数,帮redux多加一些功能,下一篇我们会提。
如果你有多个reducer要包起来,可以使用combineReducers
这个API
import {createStore} from "redux";
import {itemReducer, otherReducer} from "./reducer.js";
const store = createStore(combineReducers(
itemReducer,
otherReducer
));
export {itemStore};
Provider是react-redux提供的特殊React元件,被<Provider></Provider>
包住的元件都能恣意取用store里面的state。它的语法是
<Provider store={store}>
</Provider>
现在,我们回到所有React程序的起点,引入Provider
和刚刚建立的itemStore
,用它包住所有程序。
import { Provider } from "react-redux";
import { store } from "./model/store.js";
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);
我个人会习惯把Provider放在App内的最外层,但这里我觉得这样示意对新手比较好理解。
接着就能在里面的元件取用state了。但在function component使用Redux的方式会比较特别,必须要使用Redux提供的hook。
React-Redux提供了一个hookuseSelector
,能让我们在React function component中选取想要从Redux取得的state。
import { useSelector } from 'react-redux';
useSelector本身需要一个参数,此参数为函式,定义了你要如何从所有state中挑选你需要的state。
例如,由於刚刚我们定义的state结构为:
{
menuItemData: [
"Like的发问",
"Like的回答",
"Like的文章",
"Like的留言"
],
};
useSelector会把所有的state丢入我们定义的函式参数中,我们取得menuItemData的方式就是从参数函式把它单独取出并回传:
const menuItemData = useSelector(state => state.menuItemData);
menuItemData
变数就会是我们需要的state。
React-Redux提供的另一个hookuseDispatch
,能让我们在React function component中呼叫dispatch
函式。
import { useDispatch } from 'react-redux';
使用上很简单很单纯,先把这个函式取出来:
const dispatch = useDispatch();
然後想要更动state时直接呼叫它就可以。呼叫dispatch时记得要传「想要选择的更动规则、想要传的参数」。 详细你可以回头去看刚刚的reducer是怎麽定义的
dispatch({
type: "ADD_ITEM",
payload: {itemNew:"测试资料"}
});
dispatch({ type: "CLEAN_ITEM" });
store、reducer那些的实作跟上面的范例完全一样,我就不再写一次了。这里我只有实作把先前的menuItemData搬到Redux後,如何在MenuPage引入的作法,你可以自己试试看如何把isOpen
搬进Redux。
import React, { useReducer,useMemo,useEffect} from 'react';
import MenuItem from '../component/MenuItem';
import Menu from '../component/Menu';
import { OpenContext } from '../context/ControlContext';
import { useSelector, useDispatch } from 'react-redux';
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>
</OpenContext.Provider>
);
}
export default MenuPage;
因为Redux本身还不够用,近年来又衍伸出了各式各样的Redux版本和middleware,例如:
把redux的流程封装简化
着重於redux的非同步处理
把redux的非同步处理再更简化
以functional-programming的方式处理资料流
下一篇我们就会来聊如何使用Redux-Thunk。
Vue Instance 实体/实例 Vue Instance可以解释成实体化一个Vue的物件,每个...
Day 18 - Android Studio 如何切换Activity(分页) 昨天我们讲了如何使...
前言 在ES6之前,要使用非同步并且正确输出资料必须透过callback,然而callback不好学...
URL : https://app.hackthebox.eu/machines/6 IP : 1...
第 20 天~ 走到了三分之二了~!!! 好激动阿!! 再接再厉~ 加油! 昨天把 To Do Li...