热门前端框架的数据响应式机制对比

303 阅读10分钟

一、概述

现在前端有很多单页面框架,每个框架都有自己的特色和优势,但它们都会遵循数据驱动视图的设计理念,因此我便想总结一下不同单页面框架的数据响应式设计机制,看看它们的处理方式有哪些好的地方,和哪些不好的地方。

二、数据响应式实现原理

1. Vue2

Vue2作为国内最热门的单页面框架之一,无论是框架本身的成熟度还是生态支持都已经非常完善,可以说是目前国内单页面开发领域必学的一个框架。

Vue2的数据响应式原理是基于下面这些东西:

  • 通过Object.defineProperty()getset来监听data中属性值的变更。
  • 给每个组件实例创建一个Watcher,用来收集组件实例的变量依赖,每次触发Object.definePropertyset时,都会通知Watcher
  • Watcher知道组件发生改变后,会将收集的依赖依次进行diff比对,方便有选择的更新真实dom,尽量节省浏览器性能。

Vue2这种数据响应式的实现方式,存在下面的一些问题:

  • Object.defineProperty()是针对属性的监听,而不是整个对象,因此初始化data数据时,不存在的对象属性,是监听不到的,需要通过Vue2专门封装的Vue.$set函数来实现新增属性的监听。
  • 由于Object.defineProperty()只监听属性,因此绑定data数据监听时,需要递归执行Object.defineProperty(),这就比较耗费性能,数据层级越多,性能损耗就越大。
  • 每次数据改变时,由于存在diff比对虚拟dom的过程,因此终归会需要花费一些比对时间,这一点无法避免。
  • 每多生成一个组件实例,就需要多创建一个Watcher,这个Watcher在组件被销毁之前,会一直占用浏览器内存,因此当前装载的组件越多,浏览器被占用的额外内存就越多,电脑就有可能越卡。

2. Vue3

Vue3是为了顺应技术发展趋势,诞生的Vue最新版本框架,其与Vue2版本有较大差异,虽然通过Vue项目组的努力,Vue3Vue2代码已经做了很多兼容处理,但升级起来仍然不怎么顺滑,对大型项目而言,需要修改的地方往往还是非常的多。当然这与AngularJs转变到Angular2还是完全不一样的,起码Vue3对于Vue2代码的兼容还是做了很多努力的,而Angular2则完全无法兼容AngularJs的代码,过于激进的版本转变,直接导致了Angular2的使用者大幅减少。

Vue3近两年的发展,老实说我个人是比较失望的,拥抱Hooks,倡导组合式API,虽然是大趋势,但也让vue失去了独有的编码特色。其使用方式与React越来越像,稳定性上却暂时无法与已十分完善的React相提并论,导致近两年有不少Vue开发者转投入到React的怀抱。尤大强推的打包工具vite,虽然性能方面十分优秀,但可惜现世时间太短,第三方支持还远远无法与webpack相比,因此对于绝大多数项目而言,保险的做法还是继续使用webpack打包,贸然替换为vite所带来的风险,仍然远远大于其所带来的性能收益。当然,Vue3仍是一款非常优秀的框架,只不过现阶段对实际商业开发而言,优势不明显,相信后面会越来越好。

Vue3的数据响应式原理是基于下面这些东西:

  • 通过new Proxy()来劫持整个数据对象的变化,这一点比Object.defineProperty()要方便多了,不存在新增属性无法监听到的问题了,也不存在需要递归绑定监听的操作。
  • new Proxy()中的API与Reflect的API是相对应的,有13个可用方法,因此比Object.defineProperty()要强大了许多。
  • 还可直接通过对象自带的get/set监听简单数据的改变,方便直接。
  • 每个组件实例创建一个副作用函数effect,当触发Proxyset时,会执行副作用函数。
  • 组件的副作用函数会通知框架开始进行diff比对,而Vue3对静态节点、不同类型的动态节点都做了专门的标记,确保diff比对时忽略没有改变的地方,尽量节省性能,比对完后根据虚拟dom有改变的地方渲染真实dom

Vue3通过使用Proxy(代理)的方式,已经解决了Vue2响应式中几乎所有的缺点,并且通过尽量细粒度的比对,和优化diff算法,数据响应速度已经非常快了,总体性能上算是很优秀了。

3. React

React是FaceBook发布的前端单页面框架,自打问世以来,表现就非常强劲,在开发者中的受欢迎程度始终名列前茅。React团队也确实始终在大胆的自我革新着,时刻走在技术前沿,并不断优化着使用体验,没有辜负开发者们的期望。

React的数据响应式原理是基于下面这些东西:

  • React确切的说做的不是数据响应式,而是数据改变后,通过内部封装的setState主动提示框架需要做数据比对并更新的操作,这是个主动行为,而不是通过监听完成的。
  • setState提示框架数据发生改变后,框架会从最顶层开始用diff算法查找哪些虚拟dom发生了改变,由于React中没有对组件或者节点的标记,因此框架没法立刻知道是哪个组件哪个节点发生了改变,所以会从顶层开始一层层的查找发生改变的虚拟dom,查找到改变的地方后再更新到真实dom上。

React这种数据变更的实现方式,存在下面的一些问题:

  • 由于数据改变后,React是从顶层一级一级查找变更的,因此更新效率上要比Vue的方式差一筹,Vue2可以知道变更发生在哪一个组件,而Vue3除了知道哪个组件发生了变更,还能根据节点标记忽略未改变的地方,减少比对动作,这就省去了大量的比对时间,效率明显会比React的更新方式高。当然React推出Fiber后,渲染效率大幅提高,实际体验已经不错了。

4. Svelte

Svelte在近几年的前端领域知名度还是颇高的,因为它的作者是Rollup的开发者Rich Harris,且很多思路独树一帜。其以体积小、速度快而闻名。

Svelte的语法对比其他框架更加丝滑,整体上就像写html代码一样,编程体验非常接近原生,如下面代码:

<button on:click={handleClick}> 
  Clicked {count} {count === 1 ? 'time' : 'times'} 
</button>

<script>
  let count = 0;

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

<style>
  button {
    color: black;
  }
</style>

Svelte中的style默认是scoped的。

Svelte的数据响应式原理:

  • Svelte核心是Compiler(编译),其中代码的依赖关系是通过编译器分析出来的,依赖收集会通过编译过程分析出来,且代码中何时会触发变量更改,从而更新视图,也都是通过编译器分析出来的,所以变量在代码逻辑中没有更改的,会被编译器分析出不触发任何相关的dom变更。
  • Svelte没有diff算法虚拟dom,数据响应时直接根据依赖修改对应的真实dom
  • 具体编译器编译出来的代码长什么样子,可以参看掘友写的这篇文章:Svelte 响应式原理剖析 —— 重新思考 Reactivity

Svelte告诉了大家,不依赖diff算法虚拟dom,也可以很快的筛选要更新的节点,损耗的浏览器性能也可以很少,可以说提供了一种非常独特的一切基于编译器的思路。

这篇文章也提到了Svelte天然存在的一些问题,有兴趣的可以看看:尤雨溪-如何看待 svelte 这个前端框架

5. Solid

SolidJs,是近两年前端领域的新宠,且跟Svelte一样,主打的也是轻量高效,且其开发语法是JSX配合Hooks的方式,因此对于开发者而言开发体验也不错,所以大受欢迎。

Solid是预编译的,没有runtime,因此包的体积非常小,不过同时语法约束力度也更强。

Solid的数据响应式原理:

import { createSignal } from 'solid-js';

const Parent = () => {
  const [text, setText] = createSignal("");
  return (
    <div>
      <Label text="text">
        <div>Jhon</div>
      </Label>
    </div>
  )
};

const Label = (props) => { 
  return (
    <div>
      <div>{ props.text }</div>
      <div>{ props.children }</div>
    </div>
  );
}
  • 如代码所示,Solid通过暴露出的createSignal函数创建响应式数据,createSignal会暴露出一个数组,数组的第一个值是getter,第二个值是setter
  • Solid没有使用diff算法虚拟dom,每一个组件都是一个独立的线程,在组件的createMemocreateEffect或JSX中调用的getter,会生成追踪作用域和依赖收集。
  • Solid内部通过new Proxy()来劫持组件数据变化,当通过setter设置了新值后,getter的追踪作用域会被触发,其拿值时会对比新值相对旧值是否发生了改变,发生改变则会根据依赖收集依次改变真实dom

Solid的缺点:

  • gettercreateMemocreateEffect中以函数形式调用,而在JSXProps中以寻常变量形式调用,这种语法的不一致给人一种怪异的感觉。
  • 显示隐藏、循环等功能,不是依赖的指令完成的,而是使用组件的方式完成的,对用惯了指令的同学可能会有些不适。
  • 由于诞生时间不长,所以生态还比较差。
  • 国内使用的人不多,因此中文资料很少。

三、感言

对比的这几款单页面框架都是十分优秀的,不然也不能成为热门了。目前来讲开发商业项目,还是使用ReactVue2更稳妥一些,框架本身的稳定性和强大的第三方生态保证了它们可以支撑绝大部分商业场景。

Vue3SvelteSolid目前本身还不太稳定,且第三方生态也远不够强大,做个人项目没什么问题,但用来做需求多变的商业项目,风险还是比较高的。

SvelteSolid的出现,本身是提供了一种不依赖diff算法虚拟dom来更新数据的新思路,非常值得借鉴学习,它们为开发者们带来了更多可能。当然SvelteSolid个人感觉还是更适合中小型项目,大型项目在不使用虚拟dom的情况下,实际体积和速度都很难说。

四、参考文献

# 面试官的步步紧逼:Vue2 和 Vue3 的响应式原理比对

# Vue2升级到Vue3到底是不是一个正确的选择?(尤雨溪亲自回复解读)

# Vue3是如何升级虚拟DOM模块的?

# 带你掌握Vue3新宠——快速Diff算法

# 聊一聊 Vue3 中响应式原理

# 【Vue3】响应式原理

# react的setstate原理

# SolidJS入门

# Solid-js 基础教程

# 前端框架新人入局——Solid.js初体验

# SolidJS——前端新秀框架,性能直逼原生JS

# 深入浅出 solid.js 源码

# Svelte 响应式原理剖析 —— 重新思考 Reactivity