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

ReactSplitComponents:一种全新的React函数组件编写方式,无需Hooks

时间:2023-03-27 14:13:14 JavaScript

1。关于函数组件和Hooks的问题1.为什么是函数组件?为什么React官方推荐函数式组件?类组件“并非不可用”。因为函数式组件更符合ReactUI=f(state)的哲学。于是Hooks来了,给函数组件带来“内部变量”和“副作用”,使其功能齐全。也是一个“逻辑共享”解决方案。2.函数组件的问题因为每次调用函数都会重新创建所有的内部变量,这在开发直觉上总是不合适的。UI=f(state)看起来像一个获取状态并返回UI的纯函数。就好比米饭=电饭煲(米饭),但如果电饭煲每次煮饭都创建一个新的“电路系统”,那就违反直觉了。我们更希望f只是在简单地做饭,而其他功能已经“携带”,而不是每次都“创建”。3.Hooks的问题React为了解决创建变量的问题,提供了useState,useCallback,useMemo,useRef等,state不得不用useState包裹起来。传递给子组件的复杂数据类型(函数、数组、对象)必须用useCallback和useMemo封装(大型计算也必须用useMemo封装)。如果你需要保留变量,你必须用useRef包装它。在useEffect、useCallback、useMemo的实现中,必须要有deps。以上这些都让Hooks写起来非常反直觉。我不就是用一个变量和一个函数吗,为什么还要包起来呢?像Svelte那样写作不好吗?2.解决问题1.最直观的UI=f(state):functionDemo(state){return

{state.count}
;}2.React是这样工作的:functionDemo(props){返回
{props.count}
;}3.如果你想让组件“承载”状态和功能,而不是每次都创建一个新的,那么你不能在组件中这样写:letcount=0;constonClick=()=>{count+=1;};functionDemo(){return{count}
;}分开写破坏了统一性,不太好。组件可以不只保留外部变量,还可以写一个函数吗?4.自然而然的,我们想到了闭包(注意内部返回的是React组件):functioncreateDemo(){letcount=0;constonClick=()=>{计数+=1;};返回函数Demo(){return{count}
;};}constDemo=createDemo();此时永远不会创建onClick函数,所以没必要用useCallback包裹起来。使用闭包模式,我们已经成功地移除了对useCallback的依赖。但是闭包有一个问题:所有的组件实例共享一个数据。这当然是不能接受的。5.解决闭包的数据共享问题,动态生成每个组件实例自己的数据即可:constcreate=(fn)=>(props)=>{const[ins]=useState(()=>fn());returnins(props);};functiondemo(){return()=>
;}constDemo=create(demo);我已经写了这个,但我实际上已经完成了它......嗯?如何使用这个组件?!三、能力完善1、解决useState和组件更新:constcreate=(fn)=>(props)=>{const[,setState]=useState(false);const[ins]=useState(()=>{constatom=(initState)=>{returnnewProxy(initState,{get:(target,key)=>target[key],set:(target,key,val)=>{target[key]=val;setState((s)=>!s);returntrue;},});};returnfn({atom});});returnins(props);};functiondemo({atom}){conststate=atom({count:0,});constonClick=()=>{state.count+=1;};return()=>{const{count}=状态;返回(<>

{count}

点我);};}constDemo=create(demo);使用create函数,将响应式数据生成函数atom作为参数传入,可以用来生成响应式状态。我们成功地移除了对useState的依赖。上面已经是一个可用的组件,在这里试试:codesandbox.io/s/react-split-components-1-ycw802。解决useMemo,useRef,解决props:functiondemo({props,atom}){conststate=atom({count:0,power:()=>state.count*state.count,});constcountRef={当前:null};constonClick=()=>{const{setTheme}=props;设置主题();state.count+=1;console.log('countRef',countRef.current);};return()=>{const{theme}=props;const{计数,功率}=状态;return(<>

{theme}

{count}

{power}

点我);};}constDemo=create(demo);从函数参数中传入作为Proxy实现的道具。因为每次创建变量,都得用useMemo和useRef包裹起来,但是使用闭包时就不需要了,不会创建变量,组件自然会保留变量更新后的值。而useMemo是一种类似于监控的机制,可以使用Proxy来支持atom中的计算数据。所以,我们成功的去掉了对useMemo和useRef的依赖。上面的代码,在这里试试:codesandbox.io/s/react-split-components-2-wl46b3。解决useEffect:functiondemo({atom,onMount,onEffect}){conststate=atom({loading:true,data:null,});constgetData=()=>{request().then((res)=>{state.data=res.data;state.loading=false;});};constonReload=()=>{state.loading=true;获取数据();};onMount(()=>{console.log('mounted!');getData();});onEffect(state.data,(val,prevVal)=>{console.log('state.data',val,prevVal);});return()=>{const{loading,data}=state;return(<>

{loading?'loading...':JSON.stringify(data)}

Reloaddata);};}constDemo=create(demo);从函数参数onMount和onEffect传入。onMount在挂载时调用,只有一个回调函数参数。onEffect有两个参数,第一个参数是需要监听的数据,当数据发生变化时,会调用第二个参数的回调函数。onMount和onEffect都支持清除类似于useEffect的返回函数中的副作用(例如取消订阅)。onEffect只支持监听单个props.xxx或者state.xxx,因为props和state是响应式数据,所有回调函数中的数据总能拿到最新的,不需要放dep解决更新。在监控单个数据变化的同时,可以清晰的指出“逻辑处理”所依赖的数据变化来源,从而使代码更加清晰。所以,我们成功的去掉了对useEffect的依赖。在此处尝试:codesandbox.io/s/react-split-components-3-zw6tk使用onEffect实现订阅的示例:codesandbox.io/s/react-split-components-4-y8hn84。其他Hooks到目前为止,我们已经解决了useState、useEffect、useCallback、useMemo、useRef,这些是日常开发中最常用的。官方的Hooks还剩5个:useContext、useReducer、useImperativeHandle、useLayoutEffect、useDebugValue,就不一一处理了。4.引入ReactSplitComponents(RiC)就像高阶组件一样,这种设计模式名副其实。考虑到它是一种将“变量+逻辑”和“组件体”分开的闭包写法,所以学习了ReactServerComponents的命名格式。我把它命名为ReactSplitComponents,可以简称为RiC,这里的小i可以很好的表达“分离”的特点(主要是搜索后发现RSC,RPC,RLC,RTC都拿走了up.天哪,“split”一共只有5个字母)。ReactSplitComponents的特点:1.去除了对Hooks的依赖,但不代表纯函数组件通过闭包,自然不需要Hooks包裹。这将React开发人员从“功能组件的反直觉”和“Hooks的复杂性”中解放出来,编写类似于Svelte的纯JS直观代码。毕竟闭包是JS的一个自然特性。2.仅在编写层面,不需要ESLint支持。其实在设计useEffect的实现时,我想到了一个利用现有代码的方法:将useEffect(fn,deps)改成watch(deps,fn)。但是如果这样写,watchdeps需要ESLint插件支持(因为Hooksdeps需要插件支持,否则容易出错)。除非必要,否则不要增加实体。我们希望使实现尽可能自然、简化和直观。3、与高层组件类似,是一种“设计模式”,不是API,不需要库支持。不是官方的ReactAPI,不需要构建工具支持(比如ReactServerComponents),不需要第三方库支持(其实create可以封装它是一个npm包,但是考虑到大家都有不同的习惯和需求,所以你可以自己实现辅助功能,上面的代码可以作为参考)。React拆分组件最终代码示例:codesandbox.io/s/react-split-components-final-9ftjx5。你好,RiC让我们再看一下ReactSplitComponents(RiC)示例:functiondemo({atom}){conststate=atom({count:0,});constonClick=()=>{state.count+=1;};return()=>{const{count}=状态;return(<>

{count}

点我);};}constDemo=create(demo);GitHub:github.com/nanxiaobei/react-split-components