当前位置: 首页 > 科技观察

SolidJS生硬地说:我比React更react

时间:2023-03-17 12:38:14 科技观察

大家好,我是Kason。最近刷推文的时候,经常在“前端框架”相关的推文下出现一个小弟。我一脸憨厚,心想:“哥,你是谁啊?”查了一下,原来是一个框架作者,他的作品叫做SolidJS[1]。翻看框架的介绍,这句话成功引起了我的注意:Supportmodernfront-endfeatures,suchas:JSX,Fragments,Context,Portals,Suspense,StreamingSSR,ProgressiveHydration,ErrorBoundariesandConcurrentRendering我想你会不会是React逍遥法外的公主吧?这不能说和React相似,但完全一样,对吧?作为一个繁体中文,秉承“万事俱备”的思想,试用了一天,看了看源码,发现这个框架真是个宝库。本文将比较SolidJS与React的异同,并阐述其独特的优势。可以认识一个新的框架,对React有更深的理解。打开!乍一看很相似。让我们从“计数器”示例中看一下与React语法的区别:createSignal(0);constincrement=()=>setCount(count()+1);return({count()});}render(()=>,document.getElementById("app"));与React的区别:useState改名为createSignal,count状态由React中直接使用count改为通过方法调用,即:count()是否只是一个类React框架?别着急,我们从“编译时”、“运行时”和“响应原理”三个方面来看。编译时间差别很大。React的编译时间很“瘦”,基本上就是编译JSX语法。SolidJS采用了类似Svelte的解决方案:在编译时,将状态更新编译成一个独立的DOM操作方法。这样做有什么好处?主要有两点。一定条件下的尺寸优势你不需要为你不用的代码买单在使用React时,即使不使用Hooks,它的代码也会出现在最终编译的代码中。而在SolidJS中,未使用的函数不会出现在编译代码中。比如上面的定时器例子,有一行编译后的代码是这样的:delegateEvents(["click"]);这行代码的目的是在文档上注册点击事件代理。如果您没有在计时器中使用onClick,那么您的编译代码中就不会包含这一行。有热心网友对比了Svelte和React“源代码”和“编译代码”的体积差异,类似于编译时解决方案。横轴代表源代码量,纵轴代表编译代码量,红线代表Svelte,蓝色代表React:可以看出在临界值(业务源代码量达到120kb)之前,编译时解决方案具有一定的体积优势。由于SolidJS使用JSX来描述视图,因此它比使用类Vue模板语法的Svelte更灵活,因此它无法在编译时实现与Svelte相同的极端编译优化,使其在运行时比Svelte重一点。这给他带来了额外的好处:在实际项目中(>120kb),SolidJS的代码大小比Svelte小了大约25%。真的,因祸得福吗?更快的更新速度我们知道在React和Vue中都有一层“虚拟DOM”(在React中称为Fiber树)。每当发生更新时,就会对“虚拟DOM”进行比较(Diff算法),比较的结果会进行不同的DOM操作(增、删、改)。SolidJS和Svelte更新时,可以直接调用编译后的DOM操作方法,省去了“虚拟DOM比较”这一步消耗的时间。比如上面的定时器被点击时,从触发更新到视图变化的调用栈如下:触发事件,更新状态,更新视图,一路调用到最后,清晰明了。同样的例子放在React中,调用栈如下:左中右红绿蓝帧调用栈分别对应:处理事件比较生成Fiber树根据比较结果进行DOM操作可以看出,SolidJS的更新路径比React短很多。你为什么要问?这还得从它特殊的“响应原理”说起。响应原理假设有一个状态名,初始值为KaSong。我们想根据名称渲染一个div。SolidJS的编译代码类似:const[name,setName]=createSignal("KaSong");constel=document.createElement("div");createEffect(()=>el.textContent=name());其中createEffect类似于React的useEffect。由于回调依赖于名称,当名称改变时会触发createEffect回调,改变el.textContent,导致DOM更新。类似React:useEffect(()=>{el.textContent=name;},[name])首屏渲染结果:

卡松
接下来触发更新:setName("XiaoMing")afterupdateResult:为什么
XiaoMing
更新名字后会触发createEffect?这里没有黑魔法,只是“订阅发布”。createEffect回调依赖于名称,因此它将订阅名称更改。限于篇幅,具体实现细节下次再说。这里的关键是SolidJS的状态是“原子的”。也就是说,状态相互依赖,它们形成了局部依赖图。当一个状态改变时,依赖图中的其他状态也会改变。如果在createEffect中使用了这些依赖项,它们的更改将被订阅。当状态改变时,会执行createEffect回调,然后执行特定的DOM方法来更新视图。“真实的”。“响应式更新”指的是打哪里,李云龙称得上是行家。有同学会问,React不就是这样吗?那我问你一个问题:为什么Hooks有调用顺序不能改变的要求?为什么useEffect回调会出现关闭问题?答案已经准备好了:React只有在这些限制下才能实现“Responsive”。ToiledReact有一个可能违反直觉的知识:React不关心哪个组件触发了更新。在React中,任何一个组件触发一次更新(比如调用this.setState),所有的组件都会重新走一遍这个过程。因为需要建立一个新的Fiber树。为了减少无意义的渲染,在React中使用了一些内部优化策略来确定组件是否可以重用最后更新的Fiber节点(从而跳过渲染)。同时它也提供了很多API(比如:useMemo、PureComponent...),让开发者告诉他哪些组件可以跳过渲染。如果说SolidJS的更新过程就像画家,在屏幕上需要更新的地方随便画上几笔。那么React的更新过程就像一个人拿着相机拍一张照片,然后拍这张照片,找出与上一张拍的照片有什么不同,最后更新不同的地方。今天总结一下,我们讲了SolidJS和React的区别,主要体现在三个方面:编译时运行时响应原理不知道你喜不喜欢这个:没有Hooks顺序限制,没有useEffect闭包问题,没有fiber树,比React框架更反应灵敏?如果你问我选择哪一个?当然,谁给的工资高我就用谁。考试资料【1】SolidJS:https://github.com/solidjs/solid