大家好,我是卡颂。
最近刷推时,有个老哥经常出现在 前端框架 相干推文下。
我想:“老哥你哪位?”
一查,原来是个框架作者,作品叫 SolidJS。
翻翻框架介绍,这句话胜利吸引我的留神:
反对古代前端个性,例如:JSX, Fragments, Context, Portals, Suspense, Streaming SSR, Progressive Hydration, Error Boundaries 和 Concurrent Rendering
我推敲您不会是 React
在逃公主吧?这不能说和 React
相似,只能说齐全一样吧?
作为传统中国人,秉承 来都来了 思维,我试用了一天,又看了下源码,后果发现这个框架真是个宝藏框架。
本文会比拟 SolidJS
与React
的异同,论述他的独特劣势,看完后不晓得你会不会和我收回同样的感叹:
这几乎比 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
,那么编译后代码中就不会有这一行。
有热心网友比照了相似编译时计划的 Svelte
与React
之间 源代码 与编译后代码 的体积差别。
其中横轴代表源代码体积,纵轴代表编译后代码体积,红色线条代表Svelte
,蓝色代表React
:
可见,在临界值(业务源代码体积达到 120kb)之前,编译时计划有肯定体积劣势。
因为 SolidJS
应用 JSX
形容视图,比 Svelte
应用相似 Vue
的模版语法更灵便,所以在编译时没法做到 Svelte
一样的极致编译优化,使得其相比 Svelte
运行时更重一点。
这为他带来了额定的益处:在实在我的项目(>120kb)中,SolidJS
的代码体积比 Svelte
小 25% 左右。
还真是,因祸得福?
更快的更新速度
咱们晓得,在 React
与Vue
中存在一层 虚构 DOM(React
中叫 Fiber
树)。
每当产生更新,虚构 DOM会进行比拟(Diff
算法),比拟的后果会执行不同的 DOM
操作(增、删、改)。
而 SolidJS
与Svelte
在产生更新时,能够间接调用编译好的 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
相似 React
的useEffect
。
因为其回调内依赖了 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
树。
为了缩小无意义的 render
,React
外部有些优化策略用来判断组件是否能够复用上次更新的 Fiber
节点(从而跳过render
)。
同时,也提供了很多API
(比方:useMemo
、PureComponent
…),让开发者通知他哪些组件能够跳过render
。
如果说,SolidJS
的更新流程像一个画家,画面中哪儿须要更新就往哪儿画几笔。
那么 React
的更新流程像是一个人拿相机拍一张照片,再拿这张照片和上次拍的照片找不同,最初把不同的中央更新了。
总结
明天,咱们聊了 SolidJS
与React
的差别,次要体现在三方面:
- 编译时
- 运行时
- 响应原理
不晓得你喜爱这款:没有 Hooks
程序限度、没有 useEffect
闭包问题、没有 Fiber
树、比 React
更react
的框架么?
如果你问我选哪个?当然,哪个给工资高我用哪个。