当前位置: 首页 > Web前端 > JavaScript

精读《一种 Hooks 数据流管理方案》

时间:2023-03-27 16:08:24 JavaScript

在维护大型项目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}}>)}这样count是一个动态值,必须通过useInput(state=>({count:state.count}))获取,但不会受外层组件Rerender的影响重新赋值为1。所有的动态值都可以通过setState修改,后面会讲到。这样Input下的所有子组件都可以通过useInput访问全局数据流的数据。我们有三种访问数据的场景。一:访问传递给Input组件的onChange。因为onChange是一个不可变对象,所以可以这样访问:functionInputComponent(){const{onChange}=useInput()}第二:访问我们自定义的全局Hooks函数onCustomChange:functionInputComponent(){const{onCustomChange}=useInput()}三:访问可能变化的数据fontSize。由于我们需要在fontSize改变的时候重新渲染组件,并且我们不希望上面的两个调用方法受到fontSize的影响,所以我们需要通过以下方式访问它:functionInputComponent(){const{fontSize}=useInput(state=>({fontSize:state.fontSize}))}最后,在自定义方法中,如果我们要修改变量数据,必须通过updateStore进行封装,对外暴露,而不是调用它直接地。具体方法如下。例如,假设我们需要定义一个应用状态status,其可选值为edit和preview,那么我们可以这样定义:const{useState:useInput,Provider}=createHookStore<{dynamicValue:{isAdmin:booleanstatus:'编辑'|'preview'}}>(({getState,setState})=>{consttoggleStatus=React.useCallback(()=>{//只有管理员可以切换应用程序状态if(!getState().isAdmin){return}setState(state=>({...state,status:state.status==='edit'?'preview':'edit'}))},[getState,setState])returnReact.useMemo(()=>({toggleStatus}),[toggleStatus])})这是调用:functionInputComponent(){const{toggleStatus}=useInput()return()}整个链接也是完全自动派生的。到这里,这套数据流管理方案就完成了。总结一下全局数据的使用,最方便的就是收集一个useXXXAPI,它也可以区分静态值和动态值,访问静态值时完全不会造成重新渲染。之所以需要在Provider中定义动态值dynamicValue,是因为当动态值发生变化时,数据流中的数据会自动更新,使整个应用数据与外部动态数据同步。而这个更新步骤是通过ReduxStore完成的。本文特意不给出实现源码,有兴趣的同学可以自行实现。讨论地址为:Jingdu《一种 Hooks 数据流管理方案》·Issue#345·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生-保留署名(知识共享3.0许可)