20230529----重返学习-复合组件通信redux-redux源码-redux工程化开发-自定义hook

news/2024/11/28 3:42:50/

day-080-eighty-20230529-复合组件通信redux-redux源码-redux工程化开发-自定义hook

复合组件通信redux

  • 要想实现复合组件通信,一般采用公共状态管理方案。
  • 常见的公共状态管理方案:
    1. 官方推荐的:redux。
      • redux + react-redux + redux-logger/redux-promise/redux-saga/redux-thunk:中间件。
        • 代表:dva「redux-saga 」或 umi。
      • @reduxjs/toolkit:工具类。
    2. mobx
    3. zustand

redux的应用场景

  • redux在以下情况下更有用:
    • 在应用的大量地方,都存在大量的状态。
    • 应用状态会随着时间的推移而频繁更新。
    • 更新该状态的逻辑可能很复杂。
    • 中型和大型代码量的应用,很多人协同开发。

redux库和相关工具

  • redux是一个小型的独立js库, 但是它通常与其他几个包一起使用:
    • react-redux:react-redux是我们的官方库,它让React组件redux有了交互,可以从 store读取一些state,可以通过 dispatch actions 来更新 store
    • redux Toolkitredux Toolkit是我们推荐的编写redux逻辑的方法。 它包含我们认为对于构建redux应用程序必不可少的包和函数。 redux Toolkit构建在我们建议的最佳实践中,简化了大多数redux任务,防止了常见错误,并使编写redux应用程序变得更加容易。
    • redux DevTools 拓展Redux DevTools Extension 可以显示redux存储状态随时间变化的历史记录,这允许您有效地调试应用程序。

redux基础工作流程

  1. 创建公共的容器;

    const store = createStore([reducer])
    
    • 内部包含两个状态:公共状态和事件池。
      • 公共状态:存放各组件需要通信的信息
      • 事件池:存放许多方法–一般是让组件更新的方法
    • 在内部,只要公共状态被更改,就会通知事件池中的方法执行!
      • 目的是:当公共状态被修改,让事件池中的方法执行,实现让相关组件更新,这样组件中就可以实时获取最新的公共信息了!
    • reducer就是公共状态管理的管理员
      • 对于公共状态修改,就要通过reucer来执行
  2. 公共容器获取公共状态,然后在组件中进行渲染

    store.getState()
    
  3. 如果组件中使用了公共状态信息,则我们需要把让组件更新的函数加入到公共容器的事件池中!

    store.subscribe(组件更新函数)
    
    • 这是发布订阅模式。
  4. 想要修改公共状态,需要先通知createStore([reducer])中的reducer执行,在reducer中修改公共状态

    store.dispatch({type:'xxx'})
    
    • reducer公共状态管理的管理员

      let initial={公共状态}
      const reducer = function(state=initail,action){//state: 公共状态信息。//action: 传递的对象。return state
      }
      

实际流程

  1. 创建文件/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;
        
    1. 引入createStore用于创建公共容器。
    2. 定义初始公共状态,在reducer初始化时使用。
    3. 创建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容器中的公共状态信息。
    4. 使用createStore函数创建一个store公共容器来管理公共状态。
    5. 导出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,直接从上下文中获取即可;

  2. 创建文件/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,直接从上下文中获取即可。

  3. 在需要用到公共状态管理的文件中,通过上下文对象获取到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);
        
  4. 如果一个组件需要用到store公共容器中的状态:

    • 总体步骤思路:
      1. 获取公共容器中状态。
      2. 把更新视图的方法放到公共容器中。
        • 类组件中可以使用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();}};
        }
        
  5. 如果一个组件需要修改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插件存在的几个问题:

    1. 执行 store.getState() 获取的公共状态,和容器中的公共状态,使用的是相同的堆内存地址!

      • 问题:这样我在组件中,可以基于获取的状态,直接通过 state.xxx=xxx 就修改了容器中的状态信息,相当于绕过了 disptach 也可以直接修改状态「这样不利于状态的统一管理」!
    2. 向事件池中加入 “让组件更新的函数” 的时候,并没有做去重的处理!

    3. 在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。

  • 工程化开发步骤:

    1. 拆分和合并reducer。
      • 第一步工程化的意义:
        • 公共状态按照模块进行管理,防止各个模块下的状态冲突。
        • reducer修改状态的逻辑,也拆分到各个模块下了,方便开发和维护,以及团队协作!
          • 即修改东西,不用到一个js文件上修改了,更不容易造成冲突。

combineReducers底层处理的机制

  • combineReducers底层处理的机制
    1. 首先store容器中的公共状态,会按照设定的各个模块名,分别管理各模块下的状态。

      const reducer = combineReducers({模块名: 对应的reducer,
      });
      state={vote:{title,supNum,oppNum,},task:{title,list,}
      }
      
      • 这样我们再基于store.getState()获取的就是总状态了,想获取具体的信息,还需要找到各个模块,再去访问处理!
    2. 每一次dispatch派发的时候,会把所有模块的reducer都执行一遍。

自定义hook

  1. 新建一个以use开头的驼峰命名法的函数。
  2. 自定义hook函数内部可以使用各种hook钩子,如useState()与useEffect()。
  3. 自定义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();}};
    }
    

进阶参考


http://www.ppmy.cn/news/105210.html

相关文章

Java利用JOL工具分析对象分布

文章目录 对象的组成对象头[Header]实例数据[Instance Data]内存对齐[Padding] JOL工具分析对象Java项目引入依赖创建对象与结果分析创建简单无锁对象输出结果分析创建有属性的对象输出结果分析创建数组结果输出分析创建重量级锁对象输出结果分析 局限性 参考文章&#xff1a; …

【数据分析之道-Numpy(八)】numpy统计函数

文章目录 专栏导读1、np.mean()2、np.median()3、np.std()4、np.var()5、np.min()6、np.max()7、np.sum()8、np.prod()9、np.percentile()10、np.any()11、np.all() 专栏导读 ✍ 作者简介&#xff1a;i阿极&#xff0c;CSDN Python领域新星创作者&#xff0c;专注于分享python领…

用chatGPT来NEW个对象让“码农”的节日不再仅仅只有1024(赶鸭子上架式的成长、无效不得不立的flag)

用chatGPT来NEW个对象让“码农”的节日不再仅仅只有1024 前言一、大部分的成长都是赶鸭子上架二、节日是为了告诉自己不孤单三、做不到也要立下的flag四、New个对象吧1.php定义一个科技工作者形象2.python定义一个科技工作者形象3.javascript定义一个科技工作者形象 总结 前言 …

被黑客攻击了?无所谓,我会拔网线。。。

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 最近老是有粉丝问我&#xff0c;被黑客攻击了&#xff0c;一定要拔网线吗&#xff1f;还有…

5.30黄金空头能否延续?今日多空如何布局?

近期有哪些消息面影响黄金走势&#xff1f;今日黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周一(5月29日)进入欧市盘中&#xff0c;对美国周末达成债务上限协议的乐观情绪推动风险情绪回升&#xff0c;与此同时&#xff0c;上周公布的美国PCE数据让美联储难…

可持续能源技术改变世界

文章目录 一、你在工作或生活中接触过可持续能源技术吗&#xff1f;可以分享下你的经历与看法。二、你认为可持续能源技术的优势和挑战有哪些&#xff1f;三、你了解过可持续能源技术的应用现状吗&#xff1f;四、对于可持续能源技术真的否改变世界这个问题你怎么看&#xff1f…

Leetcode 2455 可被三整除的偶数的平均值

Leetcode 2455 可被三整除的偶数的平均值 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/average-value-of-even-numbers-that-are-divisible-by-three/description/ 博主Github&#xff1a;https://github.com/GDUT-…

创新的内容创作与电子商务完美结合!下载带有ChatGPT AI的Porto WooCommerce主题!

如果你是一位电子商务网站的所有者&#xff0c;同时追求创新和卓越的内容创作&#xff0c;那么你一定不能错过Porto主题&#xff01;Porto是一款功能强大的WooCommerce主题&#xff0c;它不仅提供了卓越的电商功能&#xff0c;还集成了ChatGPT AI内容创作&#xff0c;为你的网站…