type
status
date
slug
summary
tags
category
icon
password
Edited
Nov 13, 2024 12:27 AM
Created
Jul 29, 2024 02:26 PM
在前端开发的世界中,React 和 Vue 以其强大的生态系统和灵活性,成为了主导的框架。不过,技术总是在不断演进,新的框架如 Solid 和 Svelte 正以其创新的特性,吸引着寻求更高性能和更优解决方案的开发者。
Solid 框架,我们可以将其视为 React 和 Vue 的集大成者。它不仅采纳了 React 的 Hooks 和函数式组件的精粹,提供了一个对开发者友好的API,而且还融入了 Vue 的响应式理念,通过依赖收集和发布订阅机制,实现了高效的数据响应和更新。对于熟悉Vue响应式原理的开发者来说,Solid 的这一特性将会让他们感到非常亲切。
Solid的一个显著优势在于它的细颗粒度更新能力,这与传统的React框架有着本质的不同。React在更新时倾向于重新渲染整个组件树,而 Solid 则能够精确地定位变化,只更新必要的部分。这种更新机制的优势在于它消除了对虚拟DOM的需求,让 Solid 能够直接操作真实的DOM,从而提高性能并减少资源消耗。
面对列表渲染或 DOM 元素的拖拽等操作,开发者可能会担心是否需要重新渲染整个列表。Solid通过其内置的
<For />
和 <Index />
组件,为这类问题提供了优雅的解决方案,这些组件专为处理列表和动态数据设计,确保了高效和响应式的用户界面更新。总的来说,Solid 框架巧妙地融合了 React 和 Vue 的优点,为开发者提供了一个强大而灵活的工具集,以构建高性能和高度响应的前端应用。
作者:JinSo链接:https://juejin.cn/spost/7397278180657217577来源:稀土掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Solid 框架性能评估
在深入了解 Solid 框架之前,让我们先通过与其他前端框架的性能对比,来评估 Solid 的性能表现。
速度
启动
内存
通过这些图表,我们可以看到 Solid 的性能在多个方面都表现出色,几乎接近原生 JavaScript 的性能水平。这种卓越的性能表现,无疑为 Solid 在现代前端开发中的广泛应用提供了坚实的基础。
Solid 的世界
让我们深入探索 Solid 的世界,揭开其魔力所在。
核心 API
createSignal
createSignal
API 类似于 React 的 useState
,但它返回一个 getter
函数和一个 setter
函数。这种设计允许在调用
getter
时进行依赖收集,从而实现响应式更新。(具体的内容可以等后面讲原理的时候,详细说明)createEffect
createEffect
类似于 React 的 useEffect
,但它不需要手动指定依赖项。这是因为 createSignal
已经收集了依赖,并通过发布订阅模式自动更新。createMemo
createMemo
用于跟踪计算值,类似于 createEffect
,但它创建的是一个只读的 Signal
。这适用于需要记忆化派生状态或执行昂贵计算的场景。Derived Signal
Derived Signal
是依赖一个或多个 Signal
来产生值的函数。它们类似于 Vue 的 computed
属性,但它会在每次调用时重新计算。createMemo
与 Derived Signal
的区别createMemo
是一种响应式值,可用于记忆派生状态或昂贵的计算。它们与 Derived Signal
类似,因为它们都是响应式值,当它们的依赖关系发生变化时会自动重新计算。但是,与
Derived Signal
不同,createMemo
经过优化,仅针对其依赖项的每次更改执行一次。小结
通过上述介绍,我们了解了 Solid 的核心 API 及其响应式和细粒度更新的理念。Solid 的组件不需要像 React 那样在更新时重新执行,因为它们通过依赖收集来精确定位需要更新的部分。这一点是 Solid 与 React 的显著区别之一,也是其性能优势的关键。
再重复一遍,Solid 的组件只会执行一次。
生命周期
Solid框架提供了两个生命周期Hook,它们在功能上与React的
useEffect
相似,但在使用上更为明确和不易混淆。onMount
onMount
是 createEffect
的一个别名,专门用于执行只在组件首次渲染时运行的副作用。这个Hook确保了副作用只会在组件挂载到DOM时执行一次,类似于React的 useEffect
的第一个参数为空的情况。onCleanup
onCleanup
用于在组件卸载后执行清理操作。这包括移除事件监听器、取消定时器或清理资源等,以避免内存泄漏。Props
接着,我们来说说另一个至关重要的点:
props
,这里的 props
处理和 React 有很大的不同。通过前面核心的理解,我们知道 Solid 是基于响应式进行更新的,那么说,要监听子组件的更新,这时传递下去的
props
也应该是响应式的。我们正式介绍一下
props
:Props 对象是只读的,并且含有封装为对象 getter 的响应式属性。它们具有一致的形式,无论调用者是使用 Signal, Signal 表达式还是简单值或静态值,请时刻记住不能直接解构它们,这会导致被解构的值脱离追踪范围从而失去响应性。通常,在 Solid 的 primitive 或 JSX 之外访问
props
对象上的属性可能会失去响应性。除了解构,像是扩展运算以及 Object.assign
这样的函数也会导致失去响应性。我们可以来看一个的案例:
案例
我们通过三种
name
进行比较:props.name、解构赋值name、派生name,同时添加了useEffect,更清晰的查看哪个name
更新了。执行后,可以看到
name
、name2
可以更新,name1
没有更新。这其实很容易理解,因为函数式组件只会执行一次,
name1
又把props解构出来了,所以name1
相当于一个常量了。接下来,我们讲讲如何去解构、合并
props
,而不丢失其响应式。mergeProps
将潜在的响应式对象合并而不会失去响应式性。最常见的情况就是是为组件设置默认 props。
splitProps
拆分props而不会失去响应式性。
使用
onMount
和onCleanup
,开发者可以更精确地控制组件的生命周期,使得代码逻辑更加清晰和易于管理。这两个Hook的明确分工,有助于避免在React中使用useEffect
时可能出现的混淆。
响应式系统
Solid的响应式系统是框架的核心特性之一,理解其工作原理对于有效使用Solid至关重要。
同步更新
首先,请记住一个关键点:Solid的响应式是同步的。这意味着任何状态变更都会立即反映在DOM中,确保用户界面始终与状态同步。
batch
batch
函数用于批处理多个状态更新,确保它们可以作为单个更新事务被处理。这有助于避免不必要的DOM重新渲染和提高性能。untrack
untrack
允许你临时退出响应式上下文,执行Signal读取操作而不触发依赖跟踪。这在性能优化或特定逻辑处理时非常有用。on
on
函数为计算提供了显式依赖声明的能力。它类似于React的useEffect
中的依赖数组,但提供了更明确的依赖关系声明。允许计算不立即执行而只在第一次更改时运行。可以使用
defer
选项启用此功能。Store
在传统的前端框架中,修改对象或数组通常需要克隆新的对象以替换旧对象。Solid通过
Store
提供了一种更为高效和响应式的方法来处理状态更新。createStore
Store
是Solid中用于集中管理状态的基础设施。与React中的状态管理不同,Solid的Store
是代理对象,可以自动跟踪其属性的变化,并且支持嵌套结构。Store
的属性在被访问时才会创建Signal,这意味着只有当它们在响应式上下文中被使用时,才会建立依赖关系。
我们通过一个todo的案例来详细了解一下
createStore
的使用。结构大致为这样,一个todo列表(包含是否完成当前任务)以及一个添加todo的按钮。
接下来,我们给每一个todo添加一个完成事件。
按照正常的逻辑,我们可能会写出如下代码。
但是通过Solid,我们可以很简单的去拿到指定的todo,并进行更新。
第一次看,这种方式,可能会一头雾水,这是什么写法,但是仔细看看,其实就是一层层往对象内部进行获取值的过程。
setStore
的正常调用方式是这样的,setStore(key, newValue)
换一种方式解读,就是
setStore(key, setter)
再理解我们的这个方法,其实就是
setStore(key, getter, getter..., setter)
所以,我们再来看看我们的代码,其实就是获取
todos
,然后获取数组中对应的子todo
,最后通过setter
去更新它的值。这种方式简化了状态更新逻辑,避免了不必要的对象克隆和状态同步问题。
我们再来看看,添加todo的例子,加深一下印象。
同理,我们先看看正常逻辑写出的代码
看着还不错,也挺简单的,我们再来看看 Solid 的方式
同样的方式,我们再来解读一下,先获取
todos
,然后这里看到 store.todos.length
,可能就懵了,这是啥写法???这里 Solid 对数组进行了特殊处理,可以通过
index
的方式去访问,所以第二个 getter
访问的 index
其实就是新增的意思了,最后再加上 setter
新增的todo
对象,很轻松的就添加了一个todo
。小伙伴们可能会对数组的
getter
操作比较迷惑,我们再来看一些例子。这里再放一张官方的图
对于
Path Syntax
感兴趣的,可以深入再看看官方文档了解一下。我们再来介绍几个有关
Store
技巧的小方法。produce
produce
是Solid提供的一个实用工具,受Immer库的启发,允许在不放弃控制的情况下进行小范围的状态变更。addTodo
,也是一样的。reconcile
reconcile
函数用于在现有数据集中合并新数据,仅在检测到变化时更新,从而避免不必要的渲染。还是以一个例子来说明。
unwrap
unwrap
工具用于将响应式Store
转换为普通的JavaScript对象,这在与非响应式代码或第三方库集成时非常有用。Context
在Solid中,
Context
提供了一种在组件树中共享状态的方法。与React类似,但在某些方面提供了更灵活的处理方式。全局 Signal
再讲 Context 之前,我们先来讲一讲
全局 Signal
,小伙伴们可能回想,这是啥玩意,但是用到的都是之前的东西,甚至用法都一样,只是没有想到这种用法,被 React 所铐住了。我们都知道,Solid 的 Signal 是通过依赖收集和发布订阅进行响应式处理的,对!就是这个!
那么它其实是不受组件所控制了,和组件完全没有任何关系,绑定等等。
你完全可以把它定义在组件外面,甚至再在其他组件中去调用或者设置它。
是不是很简单,很容易就理解了。
但它有一点限制:所有计算(
effect
/momo
)都需要在响应顶层即 —— createRoot
下创建。Solid 的 render
会自动执行此操作。Context
Solid的
Context
API与React非常相似,可以轻松地在已存在的代码库中使用。组件
Solid 提供了一组内置组件,帮助开发者更高效地构建用户界面。
Show
Show
组件类似于React中的三元运算符,根据条件显示不同的元素。fallback
属性充当 else
,在传递给 when
的条件不为 true
时显示。For
For
组件用于迭代数组或可迭代对象,提供数据项和索引。Index
Index
组件与For
类似,但索引是固定的,适用于列表顺序和长度稳定的情况。<For>
VS <Index>
<For>
设计用于列表的顺序和长度可能频繁变化的情况。当<For>
中的列表值发生更改时,整个列表将重新呈现。但是,如果数组发生更改(例如元素移动位置),<For>
将通过简单地移动相应的 DOM 节点并更新索引来管理此更改。而
<Index>
设计为在以下情况下使用:列表的顺序和长度保持稳定,而不是内容可能会经常变化。当 <Index>
中的列表值发生更改时,仅更新指定索引处的内容。Switch
switch
/case
之后大致建模出 <Switch>
和 <Match>
组件。Dynamic
Dynamic
组件允许你动态渲染组件,基于传入的component
属性。Portal
Portal
组件可以将子节点渲染到存在于DOM中的任意位置,默认 body
。类似于 React 的
createPortal
。ErrorBoundary
ErrorBoundary
组件可以捕获子组件树中抛出的JavaScript错误。这些组件是Solid提供的一小部分,剩下的一些组件,我会在特定的情景使用中去讲解。
绑定
在Solid中,元素属性的绑定方式提供了一些独特的灵活性和功能。
event
Solid支持派生事件和原生事件两种形式。
此外,Solid允许使用数组语法调用事件处理程序,简化了事件处理的逻辑。
style
在Solid中,样式的设置通过
style.setProperty
封装,与传统的Element.prototype.style
对象不同。class
Solid支持使用
class
和className
属性来设置元素的类名。条件设置
使用
classList
可以方便地根据条件设置类。注意
此外,由于
classList
是伪属性,因此它不在 <div {...props} />
或 <Dynamic>
等 prop spread
中工作。ref
在Solid中,
ref
的使用方式与React类似,但更简洁。 ref 转发
不需要额外调用API,可以直接从 props
获取。指令
自定义指令。从某种意义上说,这只是 ref 的语法糖,但允许我们轻松地将多个指令附加到单个元素。下面是指令函数结构:
指令函数在渲染时但在添加到 DOM 之前调用。您可以在其中做任何您想做的事情,包括创建
Signal
、effect
等等。此处给一个案例:
异步处理
异步处理最主要的就是接口调用相关的案例了,我们在 React 也会使用异步请求相关的库,如
useRequest
等等。Solid框架提供了几种处理异步逻辑的高效方式,使得数据加载和状态管理变得更加简洁和直观。
createResource
createResource
是Solid中处理异步加载的特殊Signal,它封装了加载状态、错误处理以及数据更新的逻辑。我们详细的看一个使用的案例。
这个Resource Signal包括响应式的
loading
和error
属性,以及用于直接更新和重新获取数据的方法。这个案例很好的结合了刚刚所讲的一些组件以及异步请求相关的内容。
接着这个案例,我们再讲讲异步相关的几个组件。
Suspense
异步事件未完成时显示回退占位而不是部分加载的内容。
需要注意的是,触发Suspense
的是异步派生值的读取。不是异步获取行为本身。如果在Suspense
边界下未读取资源 Signal(包括lazy
组件),Suspense
将不会挂起
SuspenseList
要协调多个
Suspense
组件revealOrder
属性配置为forwards
来包裹内容,子组件将按照它们在树中出现的顺序呈现,而不管它们加载的顺序。这减少了页面跳转。 你可以将revealOrder
设置为backwards
或together
,backwards
将反转组件展示顺序,together
则会等待所有 Suspense 组件加载完毕
tail
选项可以设置为hidden
或collapsed
。这会覆盖显示所有回退的默认行为,要么不显示,要么显示按照revealOrder
设置的方向显示下一个
Transition
Suspense 允许我们在加载数据时显示回退内容
useTransition
来避免回到回退状态。useTransition
返回一个挂起的信号指示器和一个开始过渡(transition)的方法与
useTransition
类似,只是没有关联的挂起状态。这个可以直接用来启动 Transition
。结语
我们一同走过了Solid的奇妙世界,从基础概念到高级特性,希望这次旅程能给你带来启发和乐趣。Solid框架的确有不少让人眼前一亮的招数,不是吗?
如果你觉得这篇文章还算有趣,或者你已经迫不及待想要在你的项目中试试Solid,那就大胆去尝试吧!记得,每个伟大的作品都始于一个小小的尝试。
后续,我还会深入探索Solid的原理,更深入的了解其中的内容。
参考链接
- 作者:JinSo
- 链接:https://jinso365.top/article/solid-travel
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。