Vue3.0相比于2.x版本做了响应式系统的升级,在Vue2.x版本中是通过defineProperty去设置响应式,但在3.0中采取了Proxy的方式,这样做有三个优点
收集依赖的过程主要是通过三个函数,这三个函数分别是
首先看下面这个案例,模版中有三个P标签,其中只有最后一个P标签的TEXT部分是动态的
在之前的VDOM中,如果msg值发生改变,整个模版中的所有元素都需要重新渲染。但在Vue3.0中,在这个模版编译时,编译器会在动态标签末尾加上 /* Text*/ PatchFlag。只能带patchFlag 的 Node 才被认为是动态的元素,会被追踪属性的修改。并且 PatchFlag 会标识动态的属性类型有哪些,比如这里 的TEXT 表示只有节点中的文字是动态的。
每一个Block中的节点,就算很深,也是直接跟Block一层绑定的,可以直接跳转到动态节点而不需要逐个逐层遍历。
既有VDOM的灵活性,又有性能保证。
当使用hoistStatic时,所有 静态的节点都被提升到render方法之外。这意味着,他们只会在应用启动的时候被创建一次,而后随着每次的渲染被不停地复用。
在大型应用中对于内存有很大的优化。
正常情况下,当绑定一个事件:
静态代码
模版会被编译为
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", { onClick: _ctx.handleClick }, "静态代码", 8 /* PROPS */, ["onClick"])
]))
}
其中事件会每次从全局上下文中获取。而当开启了cacheHandler之后
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", {
onClick: _cache[1] || (_cache[1] = ($event, ...args) => (_ctx.handleClick($event, ...args)))
}, "静态代码")
]))
}
编辑器会为你动态创建一个内联函数,内联函数里面再去引用当前组件上最新的handler。之后编辑器会将内联函数缓存。每次重新渲染时如果事件处理器没有变,就会使用缓存中的事件处理而不会重新获取事件处理器。这个节点就可以被看作是一个静态的节点。这种优化更大的作用在于当其作用于组件时,之前每次重新渲染都会导致组件的重新渲染,在通过handler缓存之后,不会导致组件的重新渲染了。
当开启SSR了之后,如果我们模版中有一些静态标签,这些静态标签会被直接转化成文本。
其中的动态绑定依然是一个单独的字符串内嵌进去。这个性能肯定比React 转成VDOM在专为HTML快很多。
刚才提到在SSR中静态的节点会被转化为纯字符串。如果在客户端,当静态节点嵌套足够多的时候,VUE编译器也会将VDOM转化为纯字符串的HTML。即 StaticNode。
通过这些操作,我们可以看下,跟vue2比可以快一倍以上,内存占用可以小一倍以上。
因为ES6模块是静态引用的,所以我们可以在编译时正确的判断到底加载了哪些代码。对代码全局做一个分析,找到那些没用被用到的模块、函数、变量,并把这些去掉。
当使用一个 bundle (webpack etc.)的时候,默认会加上 TreeShaking。Vue 3.0 中没有被用到的模块可以不被打包到编译后的文件中,被 TreeShake 掉。当只有一个HelloWorld的时候 Vue3打包后 13.5kb。所有的组件全部加载进来时是 22.5kb
随着Vue组件的增大,组件内代码变得越来越难以理解和维护。其中的一些可以复用的代码很难被抽离出来。同时 Vue2.0还缺少 TS支持。在Vue2中,逻辑概念(功能)被管理在组件中,但是功能和组件并不是一对一关系。一个功能可以被多个组件使用同时一个组件可以有多个功能。在Vue中,一个功能可能需要依赖多个Options(components、props、data、computed、methods及生命周期方法)。
在 Composition API中提供可 setup 方法。以一个有搜索功能和 排序功能组件为例:
在Vue2中有几种方式可以复用代码,其中之一就是 Mixins。
Vue3中不在要求模版的跟节点必须是只能有一个节点。跟节点和和render函数返回的可以是纯文字、数组、单个节点,如果是数组,会自动转化为 Fragments。
对标 React Portal。可以做一些关于响应式的设计,如果屏幕宽度比较宽的时候,加入某些元素,屏幕变窄后移除。
等待嵌套的异步依赖。再把一个嵌套的组件树渲染到页面上之前,先在内存中进行渲染,并记录所有的存在异步依赖的组件。只有所有的异步依赖全部被resolve之后,才会把整个书渲染到dom中。当你的组件中有一个 async的 setup函数,这个组件可以被看作是一个Async Component,只有当这个组件被Resolve之后,再把整个树渲染出来
Vue3源码使用 TS重写,但不意味着vue3的项目也要使用TS。但Vue3会对 TS有更好的支持
回顾Vue2,我们知道每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把用到的数据property记录为依赖,当依赖发生改变,触发setter,则会通知watcher,从而使关联的组件重新渲染
试想一下,一个组件结构如下图
静态文本
静态文本
{{ message }}
静态文本
...
静态文本
可以看到,组件内部只有一个动态节点,剩余一堆都是静态节点,所以这里很多 diff 和遍历其实都是不需要的,造成性能浪费
因此,Vue3在编译阶段,做了进一步优化。主要有如下:
vue3在diff算法中相比vue2增加了静态标记
关于这个静态标记,其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较
下图这里,已经标记静态节点的p标签在diff过程中则不会比较,把性能进一步提高
关于静态类型枚举如下
export const enum PatchFlags {
TEXT = 1,// 动态的文本节点
CLASS = 1 << 1, // 2 动态的 class
STYLE = 1 << 2, // 4 动态的 style
PROPS = 1 << 3, // 8 动态属性,不包括类名和样式
FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 动态 solt
HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff
BAIL = -2 // 一个特殊的标志,指代差异算法
}
Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用
这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
你好
{{ message }}
没有做静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("span", null, "你好"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
做了静态提升之后
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
// Check the console for the AST
静态内容_hoisted_1被放置在render 函数外,每次渲染的时候只要取 _hoisted_1 即可
同时 _hoisted_1 被打上了 PatchFlag ,静态标记值为 -1 ,特殊标志是负整数表示永远不会用于 Diff
默认情况下绑定事件行为会被视为动态绑定,所以每次都会去追踪它的变化
没开启事件监听器缓存
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", { onClick: _ctx.onClick }, "点我", 8 /* PROPS */, ["onClick"])
// PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
]))
})
开启事件侦听器缓存后
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
}, "点我")
]))
}
上述发现开启了缓存后,没有了静态标记。也就是说下次diff算法的时候直接使用
当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染
div>
你好
... // 很多个静态属性
{{ message }}
编译后
import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer"
export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
const _cssVars = { style: { color: _ctx.color }}
_push(`你好...你好${
_ssrInterpolate(_ctx.message)
}`)
}
相比Vue2,Vue3整体体积变小了,除了移出一些不常用的API,再重要的是Tree shanking
任何一个函数,如ref、reavtived、computed等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
setup(props, context) {
const age = ref(18)
let state = reactive({
name: 'test'
})
const readOnlyAge = computed(() => age.value++) // 19
return {
age,
state,
readOnlyAge
}
}
});
vue2中采用 defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式
vue3采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历
关于这两个 API 具体的不同,我们下篇文章会进行一个更加详细的介绍
给大家分享我收集整理的各种学习资料,前端小白交流、学习交流,也可以直接问我,我会组织大家一起做项目练习,帮助大家匹配一位学习伙伴互相监督学习-下面是学习资料参考。
留言与评论(共有 0 条评论) “” |