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({ //传递的这个对象就是action type:'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函数:文章来源地址https://www.toymoban.com/news/detail-464456.htmlimport { 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"> <Button type="primary" onClick={() => { store.dispatch({ type: "sup" }); }} > 支持 </Button> <Button type="primary" danger onClick={() => { 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 true let length = !!obj && 'length' in obj && obj.length if (isFunction(obj) || isWindow(obj)) return false return length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj } // 检测是否为一个纯粹的对象(标准普通对象) const isPlainObject = function isPlainObject(obj) { if (isType(obj) !== "object") return false let proto, Ctor proto = Object.getPrototypeOf(obj) if (!proto) return true Ctor = proto.hasOwnProperty('constructor') && proto.constructor return 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 obj if (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 = +cycle if (isNaN(cycle)) cycle = 2592000000 let data = localStorage.getItem(key) if (!data) return null let { 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" + item return 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)) return arr.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], index if (!Array.isArray(arr)) return index = 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)) return for (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 = null const 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; } // other return 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都执行一遍。文章来源:https://www.toymoban.com/news/detail-464456.html
-
自定义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(); } }; }
进阶参考
到了这里,关于20230529----重返学习-复合组件通信redux-redux源码-redux工程化开发-自定义hook的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!