day-080-eighty-20230529-复合组件通信redux-redux源码-redux工程化开发-自定义hook
复合组件通信redux
- 要想实现复合组件通信,一般采用公共状态管理方案。
- 常见的公共状态管理方案:
- 官方推荐的:redux。
redux
+react-redux
+redux-logger
/redux-promise
/redux-saga
/redux-thunk
:中间件。- 代表:dva「redux-saga 」或 umi。
@reduxjs/toolkit
:工具类。
mobx
。zustand
。- …
- 官方推荐的:redux。
redux的应用场景
redux
在以下情况下更有用:- 在应用的大量地方,都存在大量的状态。
- 应用状态会随着时间的推移而频繁更新。
- 更新该状态的逻辑可能很复杂。
- 中型和大型代码量的应用,很多人协同开发。
redux库和相关工具
redux
是一个小型的独立js库
, 但是它通常与其他几个包一起使用:react-redux
:react-redux是我们的官方库,它让React组件
与redux
有了交互,可以从store
读取一些state
,可以通过dispatch
actions
来更新store
!redux Toolkit
:redux Toolkit
是我们推荐的编写redux逻辑的方法
。 它包含我们认为对于构建redux应用程序
必不可少的包和函数。redux Toolkit
构建在我们建议的最佳实践中,简化了大多数redux任务
,防止了常见错误,并使编写redux应用程序
变得更加容易。redux DevTools 拓展
:Redux DevTools Extension
可以显示redux存储
中状态
随时间变化的历史记录,这允许您有效地调试应用程序。
redux基础工作流程
-
创建公共的容器;
const store = createStore([reducer])
- 内部包含两个状态:公共状态和事件池。
- 公共状态:存放
各组件需要通信的信息
。 - 事件池:存放许多方法–一般是
让组件更新的方法
。
- 公共状态:存放
- 在内部,只要
公共状态
被更改,就会通知事件池中的方法
执行!- 目的是:当
公共状态被修改
,让事件池中的方法执行
,实现让相关组件更新
,这样组件中就可以实时获取最新的公共信息
了!
- 目的是:当
reducer
就是公共状态管理的管理员
。- 对于
公共状态
的修改
,就要通过reucer来执行
。
- 对于
- 内部包含两个状态:公共状态和事件池。
-
从
公共容器
中获取公共状态
,然后在组件中进行渲染
。store.getState()
-
如果
组件中使用了公共状态信息
,则我们需要把让组件更新的函数
加入到公共容器的事件池
中!store.subscribe(组件更新函数)
- 这是发布订阅模式。
-
想要
修改公共状态
,需要先通知createStore([reducer])
中的reducer
执行,在reducer中修改公共状态
!store.dispatch({type:'xxx'})
-
reducer
是公共状态管理的管理员
。let initial={公共状态} const reducer = function(state=initail,action){//state: 公共状态信息。//action: 传递的对象。return state }
-
实际流程
-
创建文件
/src/store/index.js
,在内部创建公共容器并导出。- 代码:
-
/src/store/index.js
:// 引入createStore用于创建公共容器。 import { createStore } from "redux";// 定义初始公共状态,在reducer初始化时使用。 let initial = {supNum: 10,oppNum: 5, };// 创建reducer管理员,用于修改容器中的公共状态。 const reducer = function reducer(state = initial, action) {// state:容器中管理的公共状态信息。实现的方式:在我们创建好store即createStore(reducer)后,redux内部会默认派发一次,也就是把reducer执行一次。而第一次执行reducer函数的时候,store容器中还没有公共状态信息,也就是此时的state=undefined。而我们写的state=initial,就是为了在此时给容器赋值初始的状态信息。// action:派发的行为对象。store.dispatch(行为对象)中传递的这个行为对象就是赋值给action形参的。而store.dispatch()一定会通知reducer函数执行。action必须是一个对象。action对象中必须具备type属性,type属性就是派发的行为标识。//我们接下来就是可以在reducer函数中,基于action.type的不同,修改不同的状态值。//函数最后返回的信息,会整体替换store容器中的公共状态信息。let { type } = action;switch (type) {case "sup":state.supNum++;break;case "opp":state.oppNum++;break;default:}return state; };// 创建一个store容器来管理公共状态。 const store = createStore(reducer); export default store;
-
- 引入createStore用于创建公共容器。
- 定义初始公共状态,在reducer初始化时使用。
- 创建reducer管理员函数,用于修改容器中的公共状态。
- reducer内部会有两个形参:
- state:容器中管理的公共状态信息。实现的方式:在我们创建好store即createStore(reducer)后,redux内部会默认派发一次,也就是把reducer执行一次。而第一次执行reducer函数的时候,store容器中还没有公共状态信息,也就是此时的state=undefined。而我们写的state=initial,就是为了在此时给容器赋值初始的状态信息。
- action:派发的行为对象。store.dispatch(行为对象)中传递的这个行为对象就是赋值给action形参的。而store.dispatch()一定会通知reducer函数执行。action必须是一个对象。action对象中必须具备type属性,type属性就是派发的行为标识。
- 我们接下来就是可以在reducer函数中,基于action.type的不同,修改不同的状态值。
- 函数最后返回的信息,会整体替换store容器中的公共状态信息。
- reducer内部会有两个形参:
- 使用createStore函数创建一个store公共容器来管理公共状态。
- 导出store公共容器对象,以便外部进行引用。
- 代码:
-
/src/store/index.js
:import { createStore } from "redux";/* 创建REDUCER管理员,修改容器中的公共状态 + state:容器中管理的公共状态信息在我们创建好store后,redux内部会默认派发一次(也就是把reducer执行一次);而第一次执行reducer函数的时候,store容器中还没有公共状态信息呢,也就是此时的state=undefined;而我们写的 state = initial,就是为了在此时给容器赋值初始的状态信息!+ action:派发的行为对象store.dispatch({//传递的这个对象就是actiontype:'xxx',...})一定会通知reducer函数执行,dispatch中传递的对象,就是赋值给action形参的+ action必须是一个对象+ action对象中必须具备 type 属性「派发的行为标识」我们接下来就可以在reducer函数中,基于 action.type 的不同,修改不同的状态值!+ 函数最后返回的信息,会整体替换store容器中的公共状态信息 */ let initial = {supNum: 10,oppNum: 5, }; const reducer = function reducer(state = initial, action) {let { type } = action;switch (type) {case "sup":state.supNum++;break;case "opp":state.oppNum++;break;default:}return state; };/* 创建STORE容器,管理公共状态 */ const store = createStore(reducer); export default store;
-
第一步store创建完毕后,我们会把 store 挂载到祖先组件的上下文中,这样以后不论哪个组件需要用到store,直接从上下文中获取即可;
-
- 代码:
-
创建文件
/src/Theme.js
,用于创建上下文对象并在根组件/src/index.jsx
中引入。将公共容器放在根组件的上下文对象中。-
代码:
-
/src/Theme.js
:import { createContext } from "react"; const Theme = createContext(); export default Theme;
-
/src/index.jsx
:// store处理 import Theme from "./Theme"; import store from "./store";// 把React用Theme.Provider包起来。 <Theme.Provider value={{ store }}>//.... </Theme.Provider>
-
-
第一步store创建完毕后,我们会把store挂载到祖先组件的上下文中,这样以后不论那个组件需要用到store,直接从上下文中获取即可。
-
-
在需要用到公共状态管理的文件中,通过上下文对象获取到store公共容器。
- 代码:
-
/src/views/Vote.jsx
:import { useContext } from "react"; import Theme from "@/Theme";//`@/`表示`/src/`;//在函数组件内部: const { store } = useContext(Theme);
-
/src/views/VoteMain.jsx
:import { useContext } from "react"; import Theme from "@/Theme";//`@/`表示`/src/`;//在函数组件内部: const { store } = useContext(Theme);
-
/src/views/VoteFooter.jsx
:import { useContext } from "react"; import Theme from "@/Theme";//`@/`表示`/src/`;//在函数组件内部: const { store } = useContext(Theme);
-
- 代码:
-
如果一个组件需要用到store公共容器中的状态:
- 总体步骤思路:
- 获取公共容器中状态。
- 把更新视图的方法放到公共容器中。
- 类组件中可以使用this.setState({})或this.forceUpdate()
- 函数组件中可以使用自定义hook来减少代码。
- 代码:
-
/src/views/Vote.jsx
:import { useContext, useState, useEffect } from "react"; import Theme from "@/Theme";//`@/`表示`/src/`;//在函数组件内部: // 获取公共状态信息 // let state = store.getState() // console.log(state); let { supNum, oppNum } = store.getState();//把让组件更新的办法放在事件池中。返回一个函数unsubscribe,此函数执行,可以把刚才放在事件池中的函数,从事件池中移除掉。 let [, setRandom] = useState(+new Date()); // setRandom(+new Date()); useEffect(() => {let unsubscribe = store.subscribe(() => {// 让视图更新即可。setRandom(+new Date());});return ()=>{unsubscribe()} }, []);
import React, { useContext, useState, useEffect } from "react"; import Theme from "@/Theme"; import VoteStyle from "./VoteStyle"; import VoteMain from "./VoteMain"; import VoteFooter from "./VoteFooter"; import useForceUpdate from "@/useForceUpdate";const Vote = function Vote() {const { store } = useContext(Theme);// 获取公共状态信息// let state = store.getState()// console.log(state);let { supNum, oppNum } = store.getState();//把让组件更新的办法放在事件池中。返回一个函数unsubscribe,此函数执行,可以把刚才放在事件池中的函数,从事件池中移除掉。let [, setRandom] = useState(+new Date());// setRandom(+new Date());useEffect(() => {let unsubscribe = store.subscribe(() => {// 让视图更新即可。setRandom(+new Date());});return () => {unsubscribe();};}, []);return (<VoteStyle><h2 className="title">React其实也不难!<span>{supNum + oppNum}</span></h2><VoteMain /><VoteFooter /></VoteStyle>); };export default Vote;
-
/src/views/Vote.jsx
- hook版:import React, { useContext } from "react"; import Theme from "@/Theme"; import VoteStyle from "./VoteStyle"; import VoteMain from "./VoteMain"; import VoteFooter from "./VoteFooter"; import useForceUpdate from "@/useForceUpdate";const Vote = function Vote() {const { store } = useContext(Theme);let { supNum, oppNum } = store.getState();let remove = useForceUpdate(store);return (<VoteStyle><h2 className="title">React其实也不难!<span>{supNum + oppNum}</span></h2><VoteMain /><VoteFooter /></VoteStyle>); }; export default Vote;
-
/src/views/VoteMain.jsx
:import { useContext, useState, useEffect } from "react"; import Theme from "@/Theme";//`@/`表示`/src/`;//在函数组件内部: let { supNum, oppNum } = store.getState();let [, setRandom] = useState(+new Date()); useEffect(() => {let unsubscribe = store.subscribe(() => {setRandom(+new Date());});return ()=>{unsubscribe()} }, []);
import React, { useContext, useState, useEffect } from "react"; import Theme from "@/Theme";const VoteMain = function VoteMain() {const { store } = useContext(Theme);let { supNum, oppNum } = store.getState();let [, setRandom] = useState(+new Date());useEffect(() => {store.subscribe(() => {setRandom(+new Date());});return;}, []);return (<div className="main-box"><p>支持人数:{supNum} 人</p><p>反对人数:{oppNum} 人</p></div>); }; export default VoteMain;
-
/src/views/VoteMain.jsx
- hook版:import React, { useContext } from "react"; import Theme from "@/Theme"; import useForceUpdate from "@/useForceUpdate";const VoteMain = function VoteMain() {const { store } = useContext(Theme);let { supNum, oppNum } = store.getState();useForceUpdate(store);return (<div className="main-box"><p>支持人数:{supNum} 人</p><p>反对人数:{oppNum} 人</p></div>); }; export default VoteMain;
-
/src/useForceUpdate.js
- hook函数:import { useState, useEffect } from "react"; export default function useForceUpdate(store) {let [, setRandom] = useState(+new Date());// 但这个useEffect还得等后期组件渲染完成后才执行内部的函数。let unsubscribe;useEffect(() => {// 组件第一次渲染完毕,把让组件更新的办法放在事件池中。// 执行subscribe会返回unsubscribe,目的是用于移除刚才加入事件池中的方法。unsubscribe = store.subscribe(() => {setRandom(+new Date());});return () => {// 组件销毁的时候,把放在事件池中的方法移除掉。unsubscribe();unsubscribe = null;};}, []);// 手动返回一个remove函数,remove可以通过闭包,访问到当前作用域中的unsubscribe,当组件渲染之后,unsubscribe就是useEffect中赋值的值。// 手动返回一个remove函数: 等待后期。return function remove() {if (unsubscribe) {unsubscribe();}}; }
-
- 总体步骤思路:
-
如果一个组件需要修改store公共容器中的状态:
- 代码:
-
/src/views/VoteFooter.jsx
:import { useContext } from "react"; import Theme from "@/Theme";//`@/`表示`/src/`;//在函数组件内部: const { store } = useContext(Theme);store.dispatch({ type: "opp" })
import React, { useContext } from "react"; import Theme from "@/Theme"; import { Button } from "antd";const VoteFooter = function VoteFooter() {const { store } = useContext(Theme);return (<div className="footer-box"><Buttontype="primary"onClick={() => {store.dispatch({ type: "sup" });}}>支持</Button><Buttontype="primary"dangeronClick={() => {store.dispatch({ type: "opp" });}}>反对</Button></div>); };export default VoteFooter;
-
- 代码:
redux源码
-
源码内有一个函数createStore()是用来创建store容器的。
- 入参reducer:是一个管理员函数,里面可以传一个状态及处理状态修改的函数。
- 返回值:
{getState,subscribe,dispatch,}
。- 返回值.getState():获取公共状态。
- 返回值.subscribe():用于让
函数
进入事件池
中,即让函数
订阅公共状态改变的事件
。 - 返回值.dispatch():派发事件,让公共状态可以被变动。
createStore()
内部有一个初始为undefined的公共状态对象和一个初始为空数组的事件池。
-
源码示例:
yarn add lodash//安装lodash。
//这个是下方要用的公共方法 (function (global, factory) {"use strict"if (typeof module === "object" && typeof module.exports === "object") {module.exports = factory(global, true)return}factory(global) })(typeof window !== "undefined" ? window : this,function factory(window, noGlobal) {/* 检测数据类型 */const toString = Object.prototype.toString,isArray = Array.isArray,typeReg = /^(object|function)$/,fnToString = Function.prototype.toString// 万能检测数据类型的方法const isType = function isType(obj) {if (obj == null) return obj + ''let type = typeof obj,reg = /^\[object (\w+)\]$/return !typeReg.test(type) ?type :reg.exec(toString.call(obj))[1].toLowerCase()}// 检测是否为对象const isObject = function isObject(obj) {return obj !== null && typeReg.test(typeof obj)}// 检测是否是window对象const isWindow = function isWindow(obj) {return obj != null && obj === obj.window}// 检测是否为函数const isFunction = function isFunction(obj) {return typeof obj === "function"}// 检测是否为数组或者伪数组const isArrayLike = function isArrayLike(obj) {if (isArray(obj)) return truelet length = !!obj && 'length' in obj && obj.lengthif (isFunction(obj) || isWindow(obj)) return falsereturn length === 0 ||typeof length === "number" && length > 0 && (length - 1) in obj}// 检测是否为一个纯粹的对象(标准普通对象)const isPlainObject = function isPlainObject(obj) {if (isType(obj) !== "object") return falselet proto, Ctorproto = Object.getPrototypeOf(obj)if (!proto) return trueCtor = proto.hasOwnProperty('constructor') && proto.constructorreturn isFunction(Ctor) && fnToString.call(Ctor) === fnToString.call(Object)}// 检测是否为空对象const isEmptyObject = function isEmptyObject(obj) {if (!isObject(obj)) throw new TypeError(`obj is not an object`)let keys = Object.getOwnPropertyNames(obj)if (typeof Symbol !== 'undefined') keys = keys.concat(Object.getOwnPropertySymbols(obj))return keys.length === 0}// 检测是否为有效数字const isNumeric = function isNumeric(obj) {let type = isType(obj)return (type === "number" || type === "string") && !isNaN(+obj)}/* 其它基础方法 */// 迭代数组/伪数组/对象「支持中途结束循环」const each = function each(obj, callback) {if (typeof callback !== "function") callback = () => { }if (typeof obj === "number" && !isNaN(obj) && obj > 0) obj = new Array(obj)if (typeof obj === "string") obj = Object(obj)if (!isObject(obj)) return objif (isArrayLike(obj)) {for (let i = 0; i < obj.length; i++) {let item = obj[i]let res = callback.call(obj, item, i)if (res === false) break}return obj}let keys = Object.getOwnPropertyNames(obj)if (typeof Symbol !== 'undefined') keys = keys.concat(Object.getOwnPropertySymbols(obj))for (let i = 0; i < keys.length; i++) {let key = keys[i],value = obj[key]let res = callback.call(obj, value, key)if (res === false) break}return obj}// 具备有效期的LocalStorage存储const storage = {set(key, value) {localStorage.setItem(key,JSON.stringify({time: +new Date(),value}))},get(key, cycle = 2592000000) {cycle = +cycleif (isNaN(cycle)) cycle = 2592000000let data = localStorage.getItem(key)if (!data) return nulllet { time, value } = JSON.parse(data)if ((+new Date() - time) > cycle) {storage.remove(key)return null}return value},remove(key) {localStorage.removeItem(key)}}// 万能的日期格式化工具const formatTime = function formatTime(time, template) {try {if (time == null) time = new Date().toLocaleString('zh-CN', { hour12: false })if (typeof template !== "string") template = "{0}/{1}/{2} {3}:{4}:{5}"let arr = []if (/^\d{8}$/.test(time)) {let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time)arr.push($1, $2, $3)} else {arr = time.match(/\d+/g)}return template.replace(/\{(\d+)\}/g, (_, $1) => {let item = arr[$1] || "00"if (item.length < 2) item = "0" + itemreturn item})} catch (_) {return ''}}// 为对象设置不可枚举的属性const define = function define(obj, key, value) {Object.defineProperty(obj, key, {writable: true,configurable: true,enumerable: false,value})}// 延迟处理函数const delay = function delay(interval = 1000) {return new Promise(resolve => {let timer = setTimeout(() => {resolve()clearTimeout(timer)}, interval)})}/* 发布订阅设计模式 */let listeners = {}// 向事件池中加入自定义事件及方法const on = function on(name, callback) {if (typeof name !== 'string') throw new TypeError('name is not a string')if (typeof callback !== 'function') throw new TypeError('callback is not a function')if (!listeners.hasOwnProperty(name)) listeners[name] = []let arr = listeners[name]if (arr.includes(callback)) returnarr.push(callback)}// 从事件池中移除自定义事件及方法const off = function off(name, callback) {if (typeof name !== 'string') throw new TypeError('name is not a string')if (typeof callback !== 'function') throw new TypeError('callback is not a function')let arr = listeners[name],indexif (!Array.isArray(arr)) returnindex = arr.indexOf(callback)if (index >= 0) arr[index] = null}// 通知指定的自定义事件(绑定的方法)执行const emit = function emit(name, ...params) {if (typeof name !== 'string') throw new TypeError('name is not a string')let arr = listeners[name]if (!Array.isArray(arr)) returnfor (let i = 0; i < arr.length; i++) {let callback = arr[i]if (typeof callback !== 'function') {arr.splice(i, 1)i--continue}callback(...params)}}/* 转移“_”的使用权 */let origin = nullconst noConflict = function noConflict() {if (window._ === utils) {window._ = origin}return utils}/* 暴露API */const utils = {isType,isObject,isArray,isArrayLike,isWindow,isFunction,isPlainObject,isEmptyObject,isNumeric,noConflict,each,storage,formatTime,define,delay,on,off,emit}if (typeof noGlobal === "undefined") {origin = window._window.utils = window._ = utils}return utils} );
import { cloneDeep } from "lodash"; // import colneDeep from "lodash/colneDeep"; import _ from "@/assets/utils";//这个是写的公共方法。// createStore:创建store容器的。 export const createStore = function createStore(reducer) {if (typeof reducer !== "function") {throw new TypeError(`reducer必须是一个函数`);}// 公共状态let state; //未来state是对象 0x001// 事件池let listeners = [];// 获取公共状态const getState = function getState() {// redux源码本身存在一个bug:基于getstate获取的公共状态信息,和容器中的state是相同的堆内存地址。这样在组件中,当我们获取公共状态后,可以绕过dispatch派发,直接通过state.xxx=xxx修改公共状态信息!// return state// 所以我们把返回的状态信息,最好进行深拷贝。// 弊端:浪费性能。return cloneDeep(state);};//向事件池中加入方法。const subscribe = function subscribe(callback) {if (typeof callback !== "function") {throw new TypeError(`callback必须是函数`);}if (!listeners.includes(callback)) {listeners.push(callback);}//返回从事件池中移除函数的方法。return function unsubscribe() {let index = listeners.indexOf(callback);if (index >= 0) {listeners.splice(index, 1);}};};// 任务派发,通知reducer执行。const dispatch = function dispatch(action) {console.log(`111-->`, 111);if (!_.isPlainObject(action)) {throw new TypeError(`action必须是一个标准对象`);}if (!("type" in action)) {throw new TypeError(`action对象必须具备type属性`);}// 通知reducer执行: 修改公共状态;state = reducer(state, action);// 公共状态更改,我们需要通知事件池中的方法执行!console.log(`1listeners-->`, listeners);let arr = listeners.slice()//防止事件执行时,更改事件池,造成事件池数组塌陷。arr.forEach((listener) => {if (typeof listener === "function") {listener();console.log(`2listener-->`, listener);}});};// 最开始默认:我们需要派发第一次,其目的是设置初始状态信息。dispatch({ type: Symbol("init-state初始化的类型") });// 返回store对象return {getState,subscribe,dispatch,}; };
import { cloneDeep } from "lodash";
import _ from "@/assets/utils";/* createStore:创建store容器的 */
export const createStore = function createStore(reducer) {if (typeof reducer !== "function")throw new TypeError(`reducer必须是一个函数`);// 公共状态let state;// 事件池let listeners = [];// 获取公共状态const getState = function getState() {// redux源码本身存在一个BUG:基于getState获取的公共状态信息,和容器中的state是相同的堆内存,这样在组件中,当我们获取公共状态后,可以绕过dispatch派发,直接通过state.xxx=xxx修改公共状态信息!// return state// 所以我们把返回的状态信息,最好进行深拷贝「弊端:浪费性能」return cloneDeep(state);};// 向事件池中加入方法const subscribe = function subscribe(callback) {if (typeof callback !== "function")throw new TypeError("callback必须是函数");if (!listeners.includes(callback)) {listeners.push(callback);}// 返回从事件池中移除函数的方法return function unsubscribe() {let index = listeners.indexOf(callback);if (index >= 0) {listeners.splice(index, 1);}};};// 任务派发,通知reducer执行const dispatch = function dispatch(action) {if (!_.isPlainObject(action))throw new TypeError("action必须是一个标准对象");if (!("type" in action)) throw new TypeError("action对象必须具备type属性");// 通知reducer执行:修改公共状态state = reducer(state, action);// 公共状态更改,我们需要通知事件池中的方法执行let arr = listeners.slice();arr.forEach((listener) => {if (typeof listener === "function") listener();});};// 最开始默认:我们需要派发第一次,其目的是设置初始状态信息dispatch({type: Symbol("INIT-STATE"),});// 返回store对象return {getState,subscribe,dispatch,};
};
redux真实源码解读
-
redux插件存在的几个问题:
-
执行 store.getState() 获取的公共状态,和容器中的公共状态,使用的是相同的堆内存地址!
- 问题:这样我在组件中,可以基于获取的状态,直接通过 state.xxx=xxx 就修改了容器中的状态信息,相当于绕过了 disptach 也可以直接修改状态「这样不利于状态的统一管理」!
-
向事件池中加入 “让组件更新的函数” 的时候,并没有做去重的处理!
-
在redux插件中,组件中只要用到了公共状态,就需要把组件更新的办法放在事件池中;后期不论哪个状态发生改变,事件池中所有的方法都会执行(所有组件都会更新),即便当前组件并没有用到修改的这个状态!!这样导致很多不必要的性能消耗!!
- 在大的机制改变不了的情况下,我们尽可能做到:
- 组件第一次渲染完毕,向事件池中加入让组件更新的办法!!
- 当组件销毁的时候,一定要把方法从事件池中移除掉!!
-
-
/node_modules/redux/dist/redux.js
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Redux = {})); }(this, (function (exports) { 'use strict';// Inlined version of the `symbol-observable` polyfill var $$observable = (function () {return typeof Symbol === 'function' && Symbol.observable || '@@observable'; })();/*** These are private action types reserved by Redux. * For any unknown actions, you must return the current state. * If the current state is undefined, you must return the initial state. * Do not reference these action types directly in your code. */ var randomString = function randomString() {return Math.random().toString(36).substring(7).split('').join('.'); };var ActionTypes = {INIT: "@@redux/INIT" + randomString(),REPLACE: "@@redux/REPLACE" + randomString(),PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {return "@@redux/PROBE_UNKNOWN_ACTION" + randomString();} };/*** @param {any} obj The object to inspect. * @returns {boolean} True if the argument appears to be a plain object. */ function isPlainObject(obj) {if (typeof obj !== 'object' || obj === null) return false;var proto = obj;while (Object.getPrototypeOf(proto) !== null) {proto = Object.getPrototypeOf(proto);}return Object.getPrototypeOf(obj) === proto; }// Inlined / shortened version of `kindOf` from https://github.com/jonschlinkert/kind-of function miniKindOf(val) {if (val === void 0) return 'undefined';if (val === null) return 'null';var type = typeof val;switch (type) {case 'boolean':case 'string':case 'number':case 'symbol':case 'function':{return type;}}if (Array.isArray(val)) return 'array';if (isDate(val)) return 'date';if (isError(val)) return 'error';var constructorName = ctorName(val);switch (constructorName) {case 'Symbol':case 'Promise':case 'WeakMap':case 'WeakSet':case 'Map':case 'Set':return constructorName;} // otherreturn type.slice(8, -1).toLowerCase().replace(/\s/g, ''); }function ctorName(val) {return typeof val.constructor === 'function' ? val.constructor.name : null; }function isError(val) {return val instanceof Error || typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number'; }function isDate(val) {if (val instanceof Date) return true;return typeof val.toDateString === 'function' && typeof val.getDate === 'function' && typeof val.setDate === 'function'; }function kindOf(val) {var typeOfVal = typeof val;{typeOfVal = miniKindOf(val);}return typeOfVal; }/*** @deprecated * * **We recommend using the `configureStore` method * of the `@reduxjs/toolkit` package**, which replaces `createStore`. * * Redux Toolkit is our recommended approach for writing Redux logic today, * including store setup, reducers, data fetching, and more. * * **For more details, please read this Redux docs page:** * **https://redux.js.org/introduction/why-rtk-is-redux-today** * * `configureStore` from Redux Toolkit is an improved version of `createStore` that * simplifies setup and helps avoid common bugs. * * You should not be using the `redux` core package by itself today, except for learning purposes. * The `createStore` method from the core `redux` package will not be removed, but we encourage * all users to migrate to using Redux Toolkit for all Redux code. * * If you want to use `createStore` without this visual deprecation warning, use * the `legacy_createStore` import instead: * * `import { legacy_createStore as createStore} from 'redux'` * */function createStore(reducer, preloadedState, enhancer) {var _ref2;if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {throw new Error('It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.');}if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedState;preloadedState = undefined;}if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error("Expected the enhancer to be a function. Instead, received: '" + kindOf(enhancer) + "'");}return enhancer(createStore)(reducer, preloadedState);}if (typeof reducer !== 'function') {throw new Error("Expected the root reducer to be a function. Instead, received: '" + kindOf(reducer) + "'");}var currentReducer = reducer;//当前管理函数。var currentState = preloadedState;//初始状态值。var currentListeners = [];//当前事件池。var nextListeners = currentListeners;//下次的事件池。var isDispatching = false;//是否正在派发事件中,如果正在派发,就暂停执行一些操作。/*** This makes a shallow copy of currentListeners so we can use* nextListeners as a temporary list while dispatching.** This prevents any bugs around consumers calling* subscribe/unsubscribe in the middle of a dispatch.*/// 让function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice();}}/*** Reads the state tree managed by the store.** @returns {any} The current state tree of your application.*/// 获取状态。function getState() {if (isDispatching) {throw new Error('You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.');}return currentState;}/*** Adds a change listener. It will be called any time an action is dispatched,* and some part of the state tree may potentially have changed. You may then* call `getState()` to read the current state tree inside the callback.** You may call `dispatch()` from a change listener, with the following* caveats:** 1. The subscriptions are snapshotted just before every `dispatch()` call.* If you subscribe or unsubscribe while the listeners are being invoked, this* will not have any effect on the `dispatch()` that is currently in progress.* However, the next `dispatch()` call, whether nested or not, will use a more* recent snapshot of the subscription list.** 2. The listener should not expect to see all state changes, as the state* might have been updated multiple times during a nested `dispatch()` before* the listener is called. It is, however, guaranteed that all subscribers* registered before the `dispatch()` started will be called with the latest* state by the time it exits.** @param {Function} listener A callback to be invoked on every dispatch.* @returns {Function} A function to remove this change listener.*/// 事件订阅事件。function subscribe(listener) {if (typeof listener !== 'function') {throw new Error("Expected the listener to be a function. Instead, received: '" + kindOf(listener) + "'");}if (isDispatching) {throw new Error('You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api/store#subscribelistener for more details.');}var isSubscribed = true;//是否正在订阅中。ensureCanMutateNextListeners();//防止数组数据塌陷。nextListeners.push(listener);return function unsubscribe() {if (!isSubscribed) {return;}if (isDispatching) {throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api/store#subscribelistener for more details.');}isSubscribed = false;ensureCanMutateNextListeners();var index = nextListeners.indexOf(listener);nextListeners.splice(index, 1);currentListeners = null;};}/*** Dispatches an action. It is the only way to trigger a state change.** The `reducer` function, used to create the store, will be called with the* current state tree and the given `action`. Its return value will* be considered the **next** state of the tree, and the change listeners* will be notified.** The base implementation only supports plain object actions. If you want to* dispatch a Promise, an Observable, a thunk, or something else, you need to* wrap your store creating function into the corresponding middleware. For* example, see the documentation for the `redux-thunk` package. Even the* middleware will eventually dispatch plain object actions using this method.** @param {Object} action A plain object representing “what changed”. It is* a good idea to keep actions serializable so you can record and replay user* sessions, or use the time travelling `redux-devtools`. An action must have* a `type` property which may not be `undefined`. It is a good idea to use* string constants for action types.** @returns {Object} For convenience, the same action object you dispatched.** Note that, if you use a custom middleware, it may wrap `dispatch()` to* return something else (for example, a Promise you can await).*/// 派发事件。function dispatch(action) {if (!isPlainObject(action)) {throw new Error("Actions must be plain objects. Instead, the actual type was: '" + kindOf(action) + "'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.");}if (typeof action.type === 'undefined') {throw new Error('Actions may not have an undefined "type" property. You may have misspelled an action type string constant.');}if (isDispatching) {throw new Error('Reducers may not dispatch actions.');}try {isDispatching = true;currentState = currentReducer(currentState, action);} finally {isDispatching = false;}var listeners = currentListeners = nextListeners;for (var i = 0; i < listeners.length; i++) {var listener = listeners[i];listener();//内部执行时,如果用unsubscribe()改变了事件池,但}return action;}/*** Replaces the reducer currently used by the store to calculate the state.** You might need this if your app implements code splitting and you want to* load some of the reducers dynamically. You might also need this if you* implement a hot reloading mechanism for Redux.** @param {Function} nextReducer The reducer for the store to use instead.* @returns {void}*/function replaceReducer(nextReducer) {if (typeof nextReducer !== 'function') {throw new Error("Expected the nextReducer to be a function. Instead, received: '" + kindOf(nextReducer));}currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.// Any reducers that existed in both the new and old rootReducer// will receive the previous state. This effectively populates// the new state tree with any relevant data from the old one.dispatch({type: ActionTypes.REPLACE});}/*** Interoperability point for observable/reactive libraries.* @returns {observable} A minimal observable of state changes.* For more information, see the observable proposal:* https://github.com/tc39/proposal-observable*/function observable() {var _ref;var outerSubscribe = subscribe;return _ref = {/*** The minimal observable subscription method.* @param {Object} observer Any object that can be used as an observer.* The observer object should have a `next` method.* @returns {subscription} An object with an `unsubscribe` method that can* be used to unsubscribe the observable from the store, and prevent further* emission of values from the observable.*/subscribe: function subscribe(observer) {if (typeof observer !== 'object' || observer === null) {throw new Error("Expected the observer to be an object. Instead, received: '" + kindOf(observer) + "'");}function observeState() {if (observer.next) {observer.next(getState());}}observeState();var unsubscribe = outerSubscribe(observeState);return {unsubscribe: unsubscribe};}}, _ref[$$observable] = function () {return this;}, _ref;} // When a store is created, an "INIT" action is dispatched so that every// reducer returns their initial state. This effectively populates// the initial state tree.dispatch({type: ActionTypes.INIT});return _ref2 = {dispatch: dispatch,subscribe: subscribe,getState: getState,replaceReducer: replaceReducer}, _ref2[$$observable] = observable, _ref2; } /*** Creates a Redux store that holds the state tree. * * **We recommend using `configureStore` from the * `@reduxjs/toolkit` package**, which replaces `createStore`: * **https://redux.js.org/introduction/why-rtk-is-redux-today** * * The only way to change the data in the store is to call `dispatch()` on it. * * There should only be a single store in your app. To specify how different * parts of the state tree respond to actions, you may combine several reducers * into a single reducer function by using `combineReducers`. * * @param {Function} reducer A function that returns the next state tree, given * the current state tree and the action to handle. * * @param {any} [preloadedState] The initial state. You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a * previously serialized user session. * If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys. * * @param {Function} [enhancer] The store enhancer. You may optionally specify it * to enhance the store with third-party capabilities such as middleware, * time travel, persistence, etc. The only store enhancer that ships with Redux * is `applyMiddleware()`. * * @returns {Store} A Redux store that lets you read the state, dispatch actions * and subscribe to changes. */var legacy_createStore = createStore;/*** Prints a warning in the console if it exists. * * @param {String} message The warning message. * @returns {void} */ function warning(message) {/* eslint-disable no-console */if (typeof console !== 'undefined' && typeof console.error === 'function') {console.error(message);}/* eslint-enable no-console */try {// This error was thrown as a convenience so that if you enable// "break on all exceptions" in your console,// it would pause the execution at this line.throw new Error(message);} catch (e) {} // eslint-disable-line no-empty}function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {var reducerKeys = Object.keys(reducers);var argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';if (reducerKeys.length === 0) {return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';}if (!isPlainObject(inputState)) {return "The " + argumentName + " has unexpected type of \"" + kindOf(inputState) + "\". Expected argument to be an object with the following " + ("keys: \"" + reducerKeys.join('", "') + "\"");}var unexpectedKeys = Object.keys(inputState).filter(function (key) {return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];});unexpectedKeys.forEach(function (key) {unexpectedKeyCache[key] = true;});if (action && action.type === ActionTypes.REPLACE) return;if (unexpectedKeys.length > 0) {return "Unexpected " + (unexpectedKeys.length > 1 ? 'keys' : 'key') + " " + ("\"" + unexpectedKeys.join('", "') + "\" found in " + argumentName + ". ") + "Expected to find one of the known reducer keys instead: " + ("\"" + reducerKeys.join('", "') + "\". Unexpected keys will be ignored.");} }function assertReducerShape(reducers) {Object.keys(reducers).forEach(function (key) {var reducer = reducers[key];var initialState = reducer(undefined, {type: ActionTypes.INIT});if (typeof initialState === 'undefined') {throw new Error("The slice reducer for key \"" + key + "\" returned undefined during initialization. " + "If the state passed to the reducer is undefined, you must " + "explicitly return the initial state. The initial state may " + "not be undefined. If you don't want to set a value for this reducer, " + "you can use null instead of undefined.");}if (typeof reducer(undefined, {type: ActionTypes.PROBE_UNKNOWN_ACTION()}) === 'undefined') {throw new Error("The slice reducer for key \"" + key + "\" returned undefined when probed with a random type. " + ("Don't try to handle '" + ActionTypes.INIT + "' or other actions in \"redux/*\" ") + "namespace. They are considered private. Instead, you must return the " + "current state for any unknown actions, unless it is undefined, " + "in which case you must return the initial state, regardless of the " + "action type. The initial state may not be undefined, but can be null.");}}); } /** * Turns an object whose values are different reducer functions, into a single * reducer function. It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed * reducer functions. * * @param {Object} reducers An object whose values correspond to different * reducer functions that need to be combined into one. One handy way to obtain * it is to use ES6 `import * as reducers` syntax. The reducers may never return * undefined for any action. Instead, they should return their initial state * if the state passed to them was undefined, and the current state for any * unrecognized action. * * @returns {Function} A reducer function that invokes every reducer inside the * passed object, and builds a state object with the same shape. */function combineReducers(reducers) {// 把传递过来的reducers对象,浅拷贝把合理的reducer项拷贝一份给finalReducers。var reducerKeys = Object.keys(reducers);//各个reducer。var finalReducers = {};for (var i = 0; i < reducerKeys.length; i++) {var key = reducerKeys[i];{if (typeof reducers[key] === 'undefined') {warning("No reducer provided for key \"" + key + "\"");}}if (typeof reducers[key] === 'function') {finalReducers[key] = reducers[key];}}// 拿到各个key。var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same// keys multiple times.var unexpectedKeyCache;{unexpectedKeyCache = {};}var shapeAssertionError;try {assertReducerShape(finalReducers);} catch (e) {shapeAssertionError = e;}return function combination(state, action) {if (state === void 0) {state = {};}if (shapeAssertionError) {throw shapeAssertionError;}{var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);if (warningMessage) {warning(warningMessage);}}var hasChanged = false;var nextState = {};for (var _i = 0; _i < finalReducerKeys.length; _i++) {var _key = finalReducerKeys[_i];var reducer = finalReducers[_key];var previousStateForKey = state[_key];var nextStateForKey = reducer(previousStateForKey, action);if (typeof nextStateForKey === 'undefined') {var actionType = action && action.type;throw new Error("When called with an action of type " + (actionType ? "\"" + String(actionType) + "\"" : '(unknown type)') + ", the slice reducer for key \"" + _key + "\" returned undefined. " + "To ignore an action, you must explicitly return the previous state. " + "If you want this reducer to hold no value, you can return null instead of undefined.");}nextState[_key] = nextStateForKey;hasChanged = hasChanged || nextStateForKey !== previousStateForKey;}hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;return hasChanged ? nextState : state;}; }function bindActionCreator(actionCreator, dispatch) {return function () {return dispatch(actionCreator.apply(this, arguments));}; } /** * Turns an object whose values are action creators, into an object with the * same keys, but with every function wrapped into a `dispatch` call so they * may be invoked directly. This is just a convenience method, as you can call * `store.dispatch(MyActionCreators.doSomething())` yourself just fine. * * For convenience, you can also pass an action creator as the first argument, * and get a dispatch wrapped function in return. * * @param {Function|Object} actionCreators An object whose values are action * creator functions. One handy way to obtain it is to use ES6 `import * as` * syntax. You may also pass a single function. * * @param {Function} dispatch The `dispatch` function available on your Redux * store. * * @returns {Function|Object} The object mimicking the original object, but with * every action creator wrapped into the `dispatch` call. If you passed a * function as `actionCreators`, the return value will also be a single * function. */function bindActionCreators(actionCreators, dispatch) {if (typeof actionCreators === 'function') {return bindActionCreator(actionCreators, dispatch);}if (typeof actionCreators !== 'object' || actionCreators === null) {throw new Error("bindActionCreators expected an object or a function, but instead received: '" + kindOf(actionCreators) + "'. " + "Did you write \"import ActionCreators from\" instead of \"import * as ActionCreators from\"?");}var boundActionCreators = {};for (var key in actionCreators) {var actionCreator = actionCreators[key];if (typeof actionCreator === 'function') {boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);}}return boundActionCreators; }function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, {value: value,enumerable: true,configurable: true,writable: true});} else {obj[key] = value;}return obj; }function ownKeys(object, enumerableOnly) {var keys = Object.keys(object);if (Object.getOwnPropertySymbols) {var symbols = Object.getOwnPropertySymbols(object);if (enumerableOnly) symbols = symbols.filter(function (sym) {return Object.getOwnPropertyDescriptor(object, sym).enumerable;});keys.push.apply(keys, symbols);}return keys; }function _objectSpread2(target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i] != null ? arguments[i] : {};if (i % 2) {ownKeys(Object(source), true).forEach(function (key) {_defineProperty(target, key, source[key]);});} else if (Object.getOwnPropertyDescriptors) {Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));} else {ownKeys(Object(source)).forEach(function (key) {Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));});}}return target; }/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */ function compose() {for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {funcs[_key] = arguments[_key];}if (funcs.length === 0) {return function (arg) {return arg;};}if (funcs.length === 1) {return funcs[0];}return funcs.reduce(function (a, b) {return function () {return a(b.apply(void 0, arguments));};}); }/** * Creates a store enhancer that applies middleware to the dispatch method * of the Redux store. This is handy for a variety of tasks, such as expressing * asynchronous actions in a concise manner, or logging every action payload. * * See `redux-thunk` package as an example of the Redux middleware. * * Because middleware is potentially asynchronous, this should be the first * store enhancer in the composition chain. * * Note that each middleware will be given the `dispatch` and `getState` functions * as named arguments. * * @param {...Function} middlewares The middleware chain to be applied. * @returns {Function} A store enhancer applying the middleware. */function applyMiddleware() {for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {middlewares[_key] = arguments[_key];}return function (createStore) {return function () {var store = createStore.apply(void 0, arguments);var _dispatch = function dispatch() {throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');};var middlewareAPI = {getState: store.getState,dispatch: function dispatch() {return _dispatch.apply(void 0, arguments);}};var chain = middlewares.map(function (middleware) {return middleware(middlewareAPI);});_dispatch = compose.apply(void 0, chain)(store.dispatch);return _objectSpread2(_objectSpread2({}, store), {}, {dispatch: _dispatch});};}; }exports.__DO_NOT_USE__ActionTypes = ActionTypes; exports.applyMiddleware = applyMiddleware; exports.bindActionCreators = bindActionCreators; exports.combineReducers = combineReducers; exports.compose = compose; exports.createStore = createStore; exports.legacy_createStore = legacy_createStore;Object.defineProperty(exports, '__esModule', { value: true });})));
redux工程化开发
-
所谓的redux工程化开发,其实就是在大型项目中,按照模块,分别管理每个模块下的 状态和reducer。
-
工程化开发步骤:
- 拆分和合并reducer。
- 第一步工程化的意义:
- 公共状态按照模块进行管理,防止各个模块下的状态冲突。
- reducer修改状态的逻辑,也拆分到各个模块下了,方便开发和维护,以及团队协作!
- 即修改东西,不用到一个js文件上修改了,更不容易造成冲突。
- 第一步工程化的意义:
- 拆分和合并reducer。
combineReducers底层处理的机制
- combineReducers底层处理的机制
-
首先store容器中的公共状态,会按照设定的各个模块名,分别管理各模块下的状态。
const reducer = combineReducers({模块名: 对应的reducer, }); state={vote:{title,supNum,oppNum,},task:{title,list,} }
- 这样我们再基于store.getState()获取的就是总状态了,想获取具体的信息,还需要找到各个模块,再去访问处理!
-
每一次dispatch派发的时候,会把所有模块的reducer都执行一遍。
-
自定义hook
- 新建一个以use开头的驼峰命名法的函数。
- 自定义hook函数内部可以使用各种hook钩子,如useState()与useEffect()。
- 自定义hook函数;
-
/src/useForceUpdate.js
- hook函数:import { useState, useEffect } from "react"; export default function useForceUpdate(store) {let [, setRandom] = useState(+new Date());// 但这个useEffect还得等后期组件渲染完成后才执行内部的函数。let unsubscribe;useEffect(() => {// 组件第一次渲染完毕,把让组件更新的办法放在事件池中。// 执行subscribe会返回unsubscribe,目的是用于移除刚才加入事件池中的方法。unsubscribe = store.subscribe(() => {setRandom(+new Date());});return () => {// 组件销毁的时候,把放在事件池中的方法移除掉。unsubscribe();unsubscribe = null;};}, []);// 手动返回一个remove函数,remove可以通过闭包,访问到当前作用域中的unsubscribe,当组件渲染之后,unsubscribe就是useEffect中赋值的值。// 手动返回一个remove函数: 等待后期。return function remove() {if (unsubscribe) {unsubscribe();}}; }