大家好,我是卡颂。

最近刷推时,有个老哥经常出现在前端框架相干推文下。

我想:“老哥你哪位?”

一查,原来是个框架作者,作品叫SolidJS。

翻翻框架介绍,这句话胜利吸引我的留神:

反对古代前端个性,例如:JSX, Fragments, Context, Portals, Suspense, Streaming SSR, Progressive Hydration, Error Boundaries和Concurrent Rendering

我推敲您不会是React在逃公主吧?这不能说和React相似,只能说齐全一样吧?

作为传统中国人,秉承来都来了思维,我试用了一天,又看了下源码,后果发现这个框架真是个宝藏框架。

本文会比拟SolidJSReact的异同,论述他的独特劣势,看完后不晓得你会不会和我收回同样的感叹:

这几乎比React还react(react译为响应)

置信看完本文后,不仅能意识一个新框架,还能对React有更深的意识。

开整!

初看很类似

让咱们从一个计数器的例子看看与React语法的差别:

import { render } from "solid-js/web";import { createSignal } from "solid-js";function Counter() {  const [count, setCount] = createSignal(0);    const increment = () => setCount(count() + 1);  return (    <button type="button" onClick={increment}>      {count()}    </button>  );}render(() => <Counter />, document.getElementById("app"));

React不同的中央:

  • useState改名成createSignal
  • 获取count状态从React中间接应用count变为通过办法调用,即:count()

难道仅仅是一个类React框架?

别急,让咱们从编译时运行时响应原理三方面来看看。

编译时大不同

React的编译时很,根本只是编译JSX语法。

SolidJS则采纳了相似Svelte的计划:在编译时,将状态更新编译为独立的DOM操作方法。

这样做有什么益处?次要有两点。

肯定条件下的体积劣势

你不须要为你没应用的代码付出代价

应用React时,即便没有用到Hooks,其代码也会呈现在最终编译后的代码中。

而在SolidJS中,未应用的性能不会呈现在编译后的代码中。

举个例子,下面计时器的例子中,编译后的代码有一行是这样:

delegateEvents(["click"]);

这行代码的目标是在document上注册click事件代理。

如果在计时器中没有应用onClick,那么编译后代码中就不会有这一行。

有热心网友比照了相似编译时计划的SvelteReact之间源代码编译后代码的体积差别。

其中横轴代表源代码体积,纵轴代表编译后代码体积,红色线条代表Svelte,蓝色代表React

可见,在临界值(业务源代码体积达到120kb)之前,编译时计划有肯定体积劣势。

因为SolidJS应用JSX形容视图,比Svelte应用相似Vue的模版语法更灵便,所以在编译时没法做到Svelte一样的极致编译优化,使得其相比Svelte运行时更重一点。

这为他带来了额定的益处:在实在我的项目(>120kb)中,SolidJS的代码体积比Svelte小25%左右。

还真是,因祸得福?

更快的更新速度

咱们晓得,在ReactVue中存在一层虚构DOMReact中叫Fiber树)。

每当产生更新,虚构DOM会进行比拟(Diff算法),比拟的后果会执行不同的DOM操作(增、删、改)。

SolidJSSvelte在产生更新时,能够间接调用编译好的DOM操作方法,省去了虚构DOM比拟这一步所耗费的工夫。

举个例子,上文的计时器,当点击后,从触发更新到视图变动的调用栈如下:

触发事件,更新状态,更新视图,一路调用走到底,清晰明了。

同样的例子放到React中,调用栈如下:

左中右红、绿、蓝框调用栈别离对应:

  • 处理事件
  • 比照并生成Fiber
  • 依据比照后果执行DOM操作

可见,SolidJS的更新门路比React短很多。

你问凭什么?这还得从其非凡的响应原理聊起。

响应原理

假如有个状态name,初始值为KaSong。咱们心愿依据name渲染一个div

SolidJS编译后的代码相似:

const [name, setName] = createSignal("KaSong");const el = document.createElement("div");createEffect(() => el.textContent = name());

其中createEffect相似ReactuseEffect

因为其回调内依赖了name,所以当name扭转后会触发createEffect回调,扭转el.textContent,造成DOM更新。

相似React的:

useEffect(() => {  el.textContent = name;}, [name])

首屏渲染后果:

<div>KaSong</div>

接下来,触发更新:

setName("XiaoMing") 

更新后后果:

<div>XiaoMing</div>

为什么更新name后会触发createEffect

这里也没有什么黑魔法,就是订阅公布

createEffect回调依赖name,所以会订阅name的变动。

因为篇幅无限,实现细节咱下回细聊。

这里的关键在于,SolidJS的状态具备原子性

即状态相互之间有依赖关系,他们造成部分的依赖图。当扭转一个状态后,依赖图中的其余状态也会扭转。

createEffect中如果应用了这些依赖,就会订阅他们的变动。

当状态扭转后,createEffect回调会执行,进而执行具体的DOM办法,更新视图。

响应式更新,指哪打哪,李云龙直呼外行。

有同学会问,React不是这样么?

那我问你个问题:

为什么Hooks会有调用程序不能变的要求?

为什么useEffect回调会有闭包问题?

答案曾经跃然纸上了:React只有在这些限度下能力实现响应式

辛苦苦干React

有一个可能反直觉的常识:React并不关怀哪个组件触发了更新。

React中,任何一个组件触发更新(如调用this.setState),所有组件都会从新走一遍流程。因为须要构建一棵新的Fiber树。

为了缩小无意义的renderReact外部有些优化策略用来判断组件是否能够复用上次更新的Fiber节点(从而跳过render)。

同时,也提供了很多API(比方:useMemoPureComponent...),让开发者通知他哪些组件能够跳过render

如果说,SolidJS的更新流程像一个画家,画面中哪儿须要更新就往哪儿画几笔。

那么React的更新流程像是一个人拿相机拍一张照片,再拿这张照片和上次拍的照片找不同,最初把不同的中央更新了。

总结

明天,咱们聊了SolidJSReact的差别,次要体现在三方面:

  • 编译时
  • 运行时
  • 响应原理

不晓得你喜爱这款:没有Hooks程序限度、没有useEffect闭包问题、没有Fiber树、比Reactreact的框架么?

如果你问我选哪个?当然,哪个给工资高我用哪个。