🎯Solid 之旅
00 分钟
2024-7-31
2024-7-31
type
status
date
slug
summary
tags
category
icon
password
Edited
Jul 31, 2024 01:13 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 的性能表现。

速度

notion image

启动

notion image

内存

notion image
 
通过这些图表,我们可以看到 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 属性,但它会在每次调用时重新计算。
 
⚠️
createMemoDerived 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更新了。
执行后,可以看到namename2可以更新,name1没有更新。
这其实很容易理解,因为函数式组件只会执行一次,name1又把props解构出来了,所以name1相当于一个常量了。
 
接下来,我们讲讲如何去解构、合并props,而不丢失其响应式。

mergeProps

将潜在的响应式对象合并而不会失去响应式性。最常见的情况就是是为组件设置默认 props。

splitProps

拆分props而不会失去响应式性。
使用onMountonCleanup,开发者可以更精确地控制组件的生命周期,使得代码逻辑更加清晰和易于管理。这两个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操作比较迷惑,我们再来看一些例子。
这里再放一张官方的图
notion image
 
对于 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支持使用classclassName属性来设置元素的类名。
 
条件设置
使用classList可以方便地根据条件设置类。
 
⚠️
注意
此外,由于 classList 是伪属性,因此它不在 <div {...props} /><Dynamic>prop spread 中工作。

ref

在Solid中,ref的使用方式与React类似,但更简洁。
ref 转发不需要额外调用API,可以直接从 props 获取。

指令

自定义指令。从某种意义上说,这只是 ref 的语法糖,但允许我们轻松地将多个指令附加到单个元素。下面是指令函数结构:
指令函数在渲染时但在添加到 DOM 之前调用。您可以在其中做任何您想做的事情,包括创建 Signaleffect等等。
此处给一个案例:
 

异步处理

异步处理最主要的就是接口调用相关的案例了,我们在 React 也会使用异步请求相关的库,如 useRequest 等等。
Solid框架提供了几种处理异步逻辑的高效方式,使得数据加载和状态管理变得更加简洁和直观。

createResource

createResource是Solid中处理异步加载的特殊Signal,它封装了加载状态、错误处理以及数据更新的逻辑。
我们详细的看一个使用的案例。
这个Resource Signal包括响应式的loadingerror属性,以及用于直接更新和重新获取数据的方法。
这个案例很好的结合了刚刚所讲的一些组件以及异步请求相关的内容。
接着这个案例,我们再讲讲异步相关的几个组件。

Suspense

异步事件未完成时显示回退占位而不是部分加载的内容。
需要注意的是,触发 Suspense 的是异步派生值的读取。不是异步获取行为本身。如果在 Suspense 边界下未读取资源 Signal(包括 lazy 组件),Suspense 将不会挂起

SuspenseList

要协调多个 Suspense 组件
  • revealOrder 属性配置为 forwards 来包裹内容,子组件将按照它们在树中出现的顺序呈现,而不管它们加载的顺序。这减少了页面跳转。 你可以将 revealOrder 设置为 backwards 或 togetherbackwards 将反转组件展示顺序,together 则会等待所有 Suspense 组件加载完毕
  • tail 选项可以设置为 hidden 或 collapsed。这会覆盖显示所有回退的默认行为,要么不显示,要么显示按照 revealOrder 设置的方向显示下一个

Transition

Suspense 允许我们在加载数据时显示回退内容
useTransition 来避免回到回退状态。
useTransition 返回一个挂起的信号指示器和一个开始过渡(transition)的方法
useTransition 类似,只是没有关联的挂起状态。这个可以直接用来启动 Transition
 

结语

我们一同走过了Solid的奇妙世界,从基础概念到高级特性,希望这次旅程能给你带来启发和乐趣。Solid框架的确有不少让人眼前一亮的招数,不是吗?
如果你觉得这篇文章还算有趣,或者你已经迫不及待想要在你的项目中试试Solid,那就大胆去尝试吧!记得,每个伟大的作品都始于一个小小的尝试。
后续,我还会深入探索Solid的原理,更深入的了解其中的内容。
 

参考链接

  1. JS框架性能对比:Solid 高居榜首,Vue、React 和 Angular 竟纷纷跌出前十_语言 & 开发_Ryan Carniato_InfoQ精选文章
  1. javascript - 精读《SolidJS》 - 前端精读专栏 - SegmentFault 思否
  1. Solid Docs (solidjs.com)
  1. Tutorials | SolidJS
 
上一篇
月度摄影 — 2024年7月 ~慢慢拍照,静静生活~
下一篇
月度摄影 — 2024年6月 ~慢慢拍照,静静生活~每日一张

评论
Loading...