提升 Svelte stores 性能的 3 个技巧

Svelte stores 系统是一个优雅但低级的完整状态管理系统的实现。 存储本身并不是很复杂,Svelte 中响应式全局状态背后的大部分“魔法”都是由 Svelte 内部的响应式模型实现的。

您实际上不必使用从 svelte/store 导出的 store 来获取响应式全局状态。 只要您有一个遵循 Store 规范的object/class ,Svelte 就会将其识别为一个store 并使用它来执行 UI 更新。

如果它长得像鸭子,游得像鸭子,叫起来像鸭子,

那么它可能是一只鸭子。

知道了这一点,我们可以期望 Svelte 回声系统能够获得处理复杂全局状态的可扩展、复杂和高性能的实现。 我们甚至不必编写一行 Svelte 代码来创建它,只要它遵循规范即可!

如果您希望我讨论在 Svelte 中创建全局状态的可扩展模式,请告诉我! 现在,我们将保持简单,并查看一些可以应用于 Svelte 提供的默认存储的概念。

1:拆分你的stores

Svelte 在“当你的应用程序状态发生变化时以外科手术式的方式更新 DOM”方面做得非常出色,但这并不是黑魔法。

假设您有一个 store 使用以下设置:

1
2
3
4
5
6
{
darkmode: true,
idle: false,
authenticated: false,
admin: false,
}

你可能认会这样做…

1
2
3
4
5
6
<script>
import { store } from './global-store'
onMount(() => {
$store.darkmode = true;
})
</script>

…只会触发监听 darkmode 属性的组件的更新,但事实是 Svelte 无法知道 darkmode 属性或 authenticated 属性是否已更新。 如果您查看 Svelte 如何“在后台”(使用普通 API)执行此更新,这将变得更容易掌握:

1
2
3
4
store.update((s) => {
s.darkmode = true // Mutate object
return s // Return updated version of object
})

请注意,返回的是整个对象,因此 Svelte 只能知道此 store 中的某些内容已更新。 因此,必须检查所有订阅 storecomponents 以确定它们的任何状态是否已更改。

因此,第一个性能提示是将 stores 拆分为更小的 stores,只保留经常在同一存储中一起使用的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// store 1
{
darkmode: true
}

// store 2
{
idle: true
}

// store 3
{
authenticated: false,
admin: false
}

使用此设置,更新 darkmode 属性只会触发订阅此 storecomponents 的更新。

2: 使用 derived(派生) stores

假设您有一个包含多个项目的结帐购物车,并且您想根据结帐列表的状态更改购物车图标的外观。

看代码。

1
2
3
4
5
6
7
<script>
import { cart } from './checkout-store'
$: isEmpty = $cart.length === 0
</script>
<a href="/checkout" style:background={isEmpty ? 'gray' : 'red'}>
Go to checkout
</a>

这个实现没有错,但我们可以做得更好! 当它们依赖的数据被更新时,反应性变量会被重新计算。 因此,如果出现以下情况,将重新计算 isEmpty 变量:

  1. 用户添加第一项
  2. 用户添加第二项
  3. 用户添加第三项
  4. 用户删除第三项

请注意,对于我们的用例,4 次重新计算中的 3 次是不必要的,因为我们只想知道购物车是否为空! 这就是派生商店出现的地方。 只有在前一个值和新值不相等时,存储才会触发更新,这意味着如果值从 false 更新为 false,则不会发生任何事情。 请注意,这主要适用于派生值是原始值的情况,因为更改是由严格的相等检查确定的。

因此,以下设置将在相同场景中减少 75% 的更新:

1
2
3
4
5
6
7
8
<script>
import { derived } from 'svelte/store'
import { cart } from './checkout-store'
const isEmpty = derived(cart, c => c.length === 0)
</script>
<a href="/checkout" style:background={$isEmpty ? 'gray' : 'red'}>
Go to checkout
</a>

3:批量更新state(状态)

当您更新 store 时,Svelte 会执行以下操作:

运行 store 更新方法

将 update 方法返回的值设置为新的 store

使用新的 store 值调用订阅者

这种方法没有任何问题,但让我们看一下以下场景:

你有 10 个组件,每个组件都监听一些全局状态。 你向服务器询问一些数据,作为回报,你得到一个包含 100 个项目的数组。 你现在可以做两件事:

  1. 循环遍历数组并添加每个项目
  2. 添加整个数组

可能不是那么明显,但是第一种方法会触发 1000 次更新(10 个组件 * 100 次更新调用),而第二种方法只会触发 10 次更新(10 个组件 * 1 次更新调用)

因此,不要执行以下操作

1
2
3
4
5
import { store } from './my-store'
// Triggers 1000 updates if there are 10 subscribers
listOfHundredItems.forEach((i) => {
store.update((s) => [...s, i])
})

你应该一直这样做:

1
2
3
import { store } from './my-store'
// Triggers 10 updates if there are 10 subscribers
store.update((s) => [...s, ...listOfHundredItems])

如果我只能选择一种优化,我会选择这个。 我研究了一个问题,即进行这种单一优化将性能时间从 30 多秒减少到 400 毫秒左右。

原英文链接:https://www.mathiaspicker.com/posts/3-tips-for-upgrading-the-performance-of-your-svelte-stores


提升 Svelte stores 性能的 3 个技巧
http://yoursite.com/2022/07/07/svelte-1/
作者
昂藏君子
发布于
2022年7月7日
许可协议