Vue Vapor:一个无虚拟DOM的Vue模式

在最近的Vue Fes大会上,Vue Vapor的作者智子大佬宣布,如果能够得到资金支持,那么Vue Vapor年底就能发布alpha版本了。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

根据尤大透露,过去一年以来智子接受赞助全职在为Vue Vapor工作。现在智子虽然还有赞助,但不再是全职的了。

也就是说现在智子大佬也需要想办法赚钱养活自己了,所以上周智子发了一个寻找赞助商的帖子。

智子的目标也很简单,能够养活他就行了,并且表示为赞助商服务,开始虽然是封闭开发,最终还是会开源的。

如果不寻求赞助,为了能够养活自己智子就只能去找一份工作了。如果这样Vapor的开发进度可能就会延缓,所以目前来说赞助计划是目前最好的方式了。

目前智子收到的赞助总额不到1000美元(包括尤大的)。

强如智子大佬,做开源也很难养活自己。欧阳也只能略尽绵薄之力(因为我最近也被通知12月底走人了,我们团队将会只剩下leader了)

现在的Vue Vapor主要有三个特点:没有虚拟DOM、高性能、更小的包体积。

没有虚拟DOM :意思很简单,就是在Vue Vapor中已经将虚拟DOM给干掉了。

高性能 :因为干掉了虚拟DOM,瓶颈得以突破,所以性能相对提高了很多.

更小的包体积 :包体积大小减少了53.3%。

并且Vue Vapor是目前大家所使用的Vue版本的子集

相比目前的Vue功能要少点,支持Composition API以及 <script setup>

因为Vapor是目前Vue版本的子集,所以无虚拟DOM的Vapor模式和有虚拟DOM的vDom模式之间是互相兼容的。

在Vapor组件中支持使用vDom模式的组件。同样在vDom组件中也支持使用Vapor模式的组件。并且还支持只使用Vapor模式的情况。

并且Vue生态中的 VueUse Vue Router Pinia Nuxt 组件库 等都会支持Vapor。

同样也支持jsx,不过需要引入 unplugin-vue-jsx-vapor

先看一个普通的操作DOM的例子:

// Initialize
const container = document.createElement('div')
const label = document.createElement('h1')
const button = document.createElement('button')
button.textContent = 'Increase'

button.addEventListener('click', increase)
let count = 0
const render = () => {
  label.textContent = `Count: ${count}`
}
function increase() {
  count++
  render() // Re-render
}
render() // Initial render

document.body.append(container)
container.append(label, button)

在这个例子中主要有两个元素: h1 标签和 button 按钮。

h1 标签中渲染了 count 变量的值。

点击 button 按钮触发click事件执行 increase 函数,在函数中首先会执行 count++ ,然后再去执行 render 函数。

render 函数中将 h1 标签中的值更新为最新值。

上面这个方案有个弊端,每次在click事件的回调中除了常规的执行 count++ 之外,还去手动调用了 render 函数。

设想一下,如果我们的业务代码里面也这样写,那代码中将会到处都在调用 render 函数了,这样的代码光想想都头疼。

还好Vue的设计中有个优秀的响应式机制,并且还将响应式的功能抽取成一个单独的包: @vue/reactivity

而Vue Vapor就是基于 @vue/reactivity 进行开发的,藉此实现当响应式数据改变后会自动更新DOM,无需去手动执行render函数。

使用 @vue/reactivity 改造后的代码如下:

import { effect, ref } from '@vue/reactivity'

// Initialize
const container = document.createElement('div')
const label = document.createElement('h1')
const button = document.createElement('button')
button.textContent = 'Increase'

button.addEventListener('click', increase)
const count = ref(0)
effect(() => {
  label.textContent = `Count: ${count.value}`
})
function increase() {
  count.value++
}

document.body.append(container)
container.append(label, button)

改造后的代码和原来的代码主要有三个不同:

  • 之前是直接使用 let count = 0 定义的变量,而改造后使用 const count = ref(0) 定义的响应式变量。

  • 之前的 increase 函数中除了执行 count++ 之外,还需要去手动调用 render() 函数。而在新的代码中只会执行 count.value++

  • 移除了render函数,替代他的是 effect 函数。在 effect 的回调函数中同样是进行DOM操作更新 h1 标签中的值。

    effect 函数和 watchEffect 很相似,当回调中的响应式变量改变后就会重新执行回调函数。这里就是当响应式变量 count 改变后会重新执行回调函数,在回调函数中进行DOM操作更新 h1 标签中的值。

这也就是Vapor基于 @vue/reactivity 实现的响应式原理,在这个过程中完全没有虚拟DOM的介入。当响应式变量改变后会执行对应的 effect 回调函数,在回调函数中直接去更新DOM即可。

看到这里有的小伙伴会有疑问,这个 effect 函数以及里面操作DOM的代码需要我们自己手写吗?

当然不需要手动去写!!在编译时Vapor会自动生成一个 effect 回调函数,以及回调函数里面更新DOM的代码。

这个是上面的例子在 Vue Vapor SFC Playground 上面经过编译后的js代码,如下图:

从上图中可以看到Vapor模式下经过编译后会自动生成一个effect回调函数,并且在回调函数中会去直接操作DOM。

至于编译时是如何生成effect回调函数,需要等Vapor稳定后欧阳会继续写文章讲解。

无虚拟DOM 的Vapor模式是 有虚拟DOM 的vDom模式的子集,并且他们之间支持component组件混用。抛弃了虚拟DOM,Vapor轻装上阵后,性能以及包体积相比传统的vDom模式有了很大的提升。最后就是智子现在在寻求赞助商,让他能够全职开发Vue Vapor的同时能够养活自己。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。

标签:游戏攻略