15

大型倒车现场:我还是写了个脏检查

 3 years ago
source link: https://zhuanlan.zhihu.com/p/280743947
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如题,用了一晚上,还是没忍住,写了个脏检查……

起因

composition API 有 ref 的缺陷,hooks API 有顺序的限制

我们企图寻找一个没有缺陷和限制的方案,幸运的是,脏检查恰好可以做到

地址在这里: yisar/dirty-check

粗粒度的单向图

脏检查曾经是一个非常好的机制,它淘汰的原因是由于 vue 文档对其进行了否定……你们懂的,但凡被 vue 否定的东西,一般都是好东西,哈哈哈

angularjs 的脏检查之所以性能差,是因为它使用了细粒度的双向图,为了找到所有的脏节点,往往会大于 n,n 为依赖的数量

我们使用粗粒度的有向图,大概率会小于 n,如果用心安排这个图,甚至可以达到 1

从算法层面不好理解的话,通俗一点说就是

我们的目标不再是找到所有的脏,而是最快地找到一个脏,然后标记整个单元(多个数据)为脏

但是我希望大家都能使用算法思维考虑问题,图这个数据结构的优化手段——约定方向,控制粒度

树是一种特殊的没有环的图,我们平时经常会对树使用上面的优化

比如 react 的单向数据流,就是约定自上而下的方向,而组件就是一种单位划分,降低粒度的行为

比如 vue3 的 block tree,block 的划分也是一种控制粒度的行为……

虽然这些东西凭直觉就可以做,但是算法思维可以让我们在同类的数据结构中找到思路

脏检查就是如此,脏检查的依赖是一个图,我们也可以使用同样的优化手段

  1. 约定方向为单向,禁止逆向收集
  2. 制造单元,降低粒度

实现

import { ref, computed, invalidate } from 'dirty-check'
const count = ref(0)
const double = computed(() => count() * 2)
count(1)
count(2)

invalidate() // 开始脏检查
console.log(double())

很重要的一个 API,就是 computed,computed 就是它的依赖发生变化了,它才会重新计算,这本身实际上是一种单元的划分

在脏检查中,我们必须要善于使用 computed 去建立单元……但是粒度还是太细了

我们可以利用框架本身的单元划分,比如组件

假如这个组件有 a b c d 四个节点,传统的检查单位是节点,我们要全部检查 a b c d,然后看看有哪个节点发生了变化……

现在我们不了,我们检查 a,发现 a 变化了,那么我们放弃检查剩下的节点了,直接标记整个组件为脏,整个组件都更新

这也是 vue2 使用的方法,依赖收集的优势在于细粒度,对于粗粒度,其实两者的性能差异不大,除非你一个组件写十万个节点,而且只有最后一个节点发生了变化

编译

前面说了辣么多,只是为了说明,图这个数据结构的性能不能被一棍子打死……

但是真正的重点不是性能,而是脏检查这个机制比其他机制更适用于编译……

<script>
  let count = 0
  let double = count * 2

  function handleClick() {
    count++
  }
</script>

<button @click={handleClick}>{double}</button>

大约编译成这个样子:

<script>
  let count = ref(0)
  let double = computed(() => count() * 2)

  function handleClick() {
    count(count() + 1)
  }
</script>

<button onclick=handleClick()>double()</button>

整个编译过程非常干净纯粹,语义几乎是没有变化的,更不需要任何语法糖……

关键这个思路 runtime 超级轻量……

倒不是说 vue 的对象劫持有什么不好,只是在编译手段面前,对象劫持的优势体现不出来,缺点倒是淋漓尽致……

权衡

我要说一句名言:

权衡的前提是保命,接受限制和平庸,但不能忍受缺陷

不同的机制有不同的优势,适用于不同的场景,我不会将对象劫持一棒子打死,但是在编译+组合灵活性的场景下,脏检查更优

以上差不多就这些啦,经过这一顿折腾,我有点理解 angular 的艺术了……

如果那个时代,大家也能和现在一样追求组合,如果它可以在文档里写出优势,大概就不会淘汰了吧

总结

脏检查更利于组合

有几个问题:

  1. fre 会使用脏检查吗?

当然不会,fre 是一个 runtime 框架,不打算使用 template,在没有编译的时候,脏检查就是个丑小鸭,而且 hooks API 只是 fre 的辅助,组合不是 fre 的方向

2. 会写一个新框架吗?

有可能会,但它会更像 svelte,探索基于编译的,纯粹的,组合


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK