在维护大型项目ORUI组件模块时,肯定会遇到全局数据传输问题。在维护项目时,比如全局用户信息、全局项目配置、全局功能配置等,都是跨模块复用的全局数据。维护UI组件时,调用组件只有一个入口,但组件会不断拆模块、分文件。对于这些组件中的模块,入口文件的参数也是全局数据。这时候一般有三种解决方案:props透传。语境。全局数据流。props透传方案,因为任何一个节点断开都会导致参数传输失败,所以维护成本和精神负担特别大。上下文,即useContext使用上下文共享全局数据。问题是更新粒度太粗了,在同一个上下文中任何值的变化都会引起重新渲染。还有一个比较Hack的方案use-context-selector,不过这个和下面说的全局数据流很像。全局数据流是一种利用react-redux等工具绕过React更新机制进行全局数据传输的方案。该方案较好地解决了项目问题,但很少有组件会使用它。过去有很多使用Redux进行本地数据流的方案,但本质上仍然是全局数据流。现在react-redux支持局部作用域方案:import{shallowEqual,createSelectorHook,createStoreHook}from'react-redux'constcontext=React.createContext(null)constuseStore=createStoreHook(context)constuseSelector=createSelectorHook(context)constuseDispatch=createDispatchHook(context)因此,借机梳理数据流管理方案,为项目和组件做一个通用的数据流管理方案。精读对于项目和组件,数据流包含两种数据:可变数据。不可变数据。对于项目,变量数据的来源是:全局外部参数。全局项目自定义变量。不可变数据的来源是:操作数据或行为的功能方法。全局外部参数是指那些不受项目代码控制的参数,比如登录用户信息数据。全局项目自定义变量由项目代码控制,比如定义一些模型数据和状态数据。对于组件,变量数据的来源包括:调用组件时传递的参数。全局组件自定义变量。不可变数据的来源包括:调用组件时传递的参数。操作数据或行为的功能方法。对于组件,调用时传递的参数可能是可变数据,也可能是不可变数据。例如,传入的props.color可能是可变数据,而props.defaultValue和props.onChange是不可变数据。整理完项目和组件的全局数据后,我们可以按照注册和调用两个步骤来设计数据流管理规范。数据流调用我们先来看调用。为了同时保证使用的便利性和应用程序的性能,我们希望使用统一的APIuseXXX来访问所有的全局数据和方法,并且满足:{}=useXXX()只能引用不可变数据,包括变量和方法。{value}=useXXX(state=>({value:state.value}))可以引用变量数据,但必须通过选择器调用。比如一个应用叫gaea,那么useGaea就是这个应用全局数据的唯一访问权限。我可以这样调用组件中的数据和方法:constPanel=()=>{//appId是应用程序的不可变数据,所以即使是变量也可以直接获取,因为它不会变,会notcausere-rendering//fetchData是一个fetch函数,发送的是内置的appId,所以绑定了一定的上下文,也是不可变数据const{appId,fetchData}=useGaea()//主题色可能在运行时被修改,只能通过选择器获取//这时候这个组件会在颜色变化的时候额外重新渲染const{color}=useGaea(state=>({color:state.theme?.color}))}比如一个组件叫Menu,那么useMenu就是这个组件的全局数据调用入口,可以这样使用://SubMenu是Menu组件的子组件,可以直接使用useMenuconstSubMenu=()=>{//defaultValue是一次性的值,所以在处理的时候做不可变的处理,这里是alreadyimmutabledata//onMenuClick是回调函数,无论参数引用如何变化,都作为不可变引用处理const{defaultValue,onMenuClick}=useMenu()//disabled是menu的参数,需要立即响应当它改变时,所以它是可变数据const{disabled}=useMenu(state=>({disabled:state.disabled}))//selectedMenu是Menu组件的内部状态,也称为const{selectedMenu}=useMenu(state=>({selectedMenu:state.selectedMenu}))}作为变量数据。可以发现,在整个应用或组件的使用范围内,做了一层抽象,即不关心数据是怎么来的,只关心数据是否可变。这样对于组件或者应用来说,内部状态可以随时开放给API层,根本不需要修改内部代码。数据流注册在注册数据流时,我们只需要定义三个参数:dynamicValue:动态参数,只能通过useInput(state=>state.xxx)访问。staticValue:静态参数,引用永远不会改变,可以直接通过useInput().xxx访问。自定义钩子,入参为staticValuegetStatesetState,这里可以封装自定义方法,定义的方法必须是静态的,可以直接通过useInput().xxx访问。const{useState:useInput,Provider}=createHookStore<{dynamicValue:{fontSize:number}staticValue:{onChange:(value:number)=>void}}>(({staticValue})=>{constonCustomChange=React.useCallback((value:number)=>{staticValue.onChange(value+1)},[staticValue])returnReact.useMemo(()=>({onCustomChange}),[onCustomChange])})上面的方法暴露了Provider和useInput的两个对象,我们首先要在组件中给它传递数据。例如,如果我写一个组件Input,我可以这样调用它:)}如果我们只想给一些动态数据赋初值,可以使用defaultDynamicValue:count:1}}>
