使用 react 的 hooks 进行全局的状态管理
React 最新正式版已经支持了 Hooks API,先快速过一下新的 API 和大概的用法。
// useState,简单粗暴,setState可以直接修改整个state const [state,setState] = useState(value); // useEffect,支持生命周期 useEffect(()=>{ // sub return ()=>{ // unsub } },[]); // useContext,和 React.createConext() 配合使用。 // 父组件使用 Context.Provider 生产数据,子组件使用 useContext() 获取数据。 const state = useContext(myContext); // useReducer,具体用法和redux类似,使用dispatch(action)修改数据。 // reducer中处理数据并返回新的state const [state, dispatch] = useReducer(reducer, initialState); // useCallback,返回一个memoized函数,第二个参数类似useEffect,只有参数变化时才会更改。 const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], ); // useMemo,返回一个memoized值,只有第二个参数发生变化时才会重新计算。类似 useCallback。 // useCallback(fn,inputs) 等效 useMemo(() => fn,inputs)。 const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]) // useRef,返回一个可变的ref对象 const refContainer = useRef(initialValue); // useImperativeMethods,详情自行查阅文档 // useMutationEffect,类似useEffect,详情自行查阅文档 // useLayoutEffect,类似useEffect,详情自行查阅文档
一开始以为使用 useState 分离数据层 和 UI 层,就可以达到数据共享了,后来发现想的太简单了。分离只是逻辑共享,数据都是独立的。
后来发现有个 useReducer
似乎和 redux 很像,然而本质上,还是 useState 实现的。
再继续探索发现,基于useContext
,同时配合useReducer
一起使用。
但是,这个方案的缺陷是,当数据太大,组件太多,会直接导致渲染性能下降。
每一次state的变化,都会从顶层组件传递下去,性能影响比较大。
当然也有一些优化手段,比如使用memo()或者useMemo(),又或者拆分更细粒度的context,对应不同的数据模块,再包装成不同的ContextProvider,只是这样略显繁琐了。
后面,终于找到一位大神的作品,经过简单的修改后得出一个可以使用的办法:
import { useState,useEffect } from 'react'; const isFunction = fn => typeof fn === 'function'; const isObject = o => typeof o === 'object'; const isPromise = fn => { if (fn instanceof Promise) return true; return isObject(fn) && isFunction(fn.then); }; // Model 类 class Model { constructor({initialState,actions}){ this.state = initialState; this.actions = {}; this.queue = []; Object.keys(actions).forEach((name)=>{ this.actions[name] = (arg)=>{ const res = actions[name].call(this,this.state,arg); if(isPromise(res)){ Promise.resolve(res).then((ret)=>{ this.state = ret; this.onDataChange(); }); }else{ this.state = res; this.onDataChange(); } } }); } useStore(){ const [, setState] = useState(); // 使用useEffect实现发布订阅 useEffect(() => { const index = this.queue.length; this.queue.push(setState); // 订阅 return () => { // 组件销毁时取消 this.queue.splice(index, 1); }; },[]); return [this.state, this.actions]; } onDataChange(){ const queues = [].concat(this.queue); queues.forEach((setState)=>{ setState(this.state); // 通知所有的组件数据变化 }); } } // models/user.js const sleep = async t => new Promise(resolve => setTimeout(resolve, t)); const initialState = {}; const actions = { async setUserInfo(){ await sleep(2000); return Promise.resolve({name:"mannymu",age:18}); }, setAge(state,age){ return Object.assign({},{...state,age}); }, setName(state,name){ return Object.assign({},{...state,name}); }, loginOut(){ return null; } } const user = new Model({ initialState, actions }); // 组件 import {useStore} from '../models/user'; const Person = ()=>{ const [state,actions] = useStore(); return ( <div> <span> My name is {state.name}.</span> <button onClick={()=> actions.setName('han meimei.')}>btn1</button> </div> ) };
这样就可以使用简单的代码,管理复杂的状态了。同时支持异步。
当然这里是直接采用的返回值覆盖 state ,如果异常情况(ajax或者其他报错)处理的不够好,可能会出现奇怪的问题,报错后 return 的值如果不确定,就会出现奇怪效果。
可以参考 redux 的办法,再扩展一个reducer 来专门修改数据,action 只负责触发修改。
参考文档:https://blog.csdn.net/tzllxya/article/details/92798097
参考github: https://github.com/yisbug/iostore 有兴趣的可以看看,这份代码,质量很高,值得一读
通过查看那位大佬在 github 上面的完整版状态管理插件源码。其基于上面的代码基本原理。做了 Proxy 的封装,就可以像 vue 一样,直接修改 this.xxx 来更新视图层。
最后,附上我的本地文件夹分类:
其中 store.js 里面,是上面的 Model 类。