Vue 3.0
Vue3.0六大亮点
- Performance:性能比Vue 2.x快1.2~2倍
- Tree shaking support:按需编译,体积比Vue2.x更小
- Composition API: 组合API(类似React Hooks)
- Better TypeScript support:更好的 Ts 支持
- Custom Renderer API:暴露了自定义渲染API
- Fragment, Teleport(Protal), Suspense:更先进的组件
Vue2.0 和 vue3.0 对比
- Vue2对TypeScript支持不友好(所有属性都放在了this对象上,难以推倒组件的数据类型)
- 大量的API挂载在Vue对象的原型上,难以实现TreeShaking。
- 架构层面对跨平台dom渲染开发支持不友好
- CompositionAPI。受ReactHook启发
- 对虚拟DOM进行了重写、对模板的编译进行了优化操作..
- 响应数据用 Proxy 代替 defineProperty
- 在 bundle 包大小方面(tree-shaking 减少了 41% 的体积),初始渲染速度方面(快了 55%),更新速度方面(快了 133%)以及内存占用方面(减少了 54%)都有着显著的性能提升[16]
Vue3.0 Api上的部分差异
- Filters 已从 Vue 3.0 中删除,不再受支持。(用计算属性或方法代替过滤器)
- $on,$off 和 $once 实例方法已被移除,应用实例不再实现事件
- destroyed 生命周期选项被重命名为 unmounted,beforeDestroy 生命周期选项被重命名为 beforeUnmount
- 没有特殊指令的标记 (v-if/else-if/else、v-for 或 v-slot) 现在被视为普通元素,并将生成原生的 元素,而不是渲染其内部内容。
- 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:
- 两者作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级。
- x 版本中 v-if 总是优先于 v-for 生效(平时写代码时还是避免用在同一元素上)
引入事件库
1
2
3
4
5
6import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// fire an event
emitter.emit('foo', { a: 'b' })vue模版
1
2
3
4
5<tempate>
<div> header </div>
<div> body </div>
<div> footer </div>
</template>-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54<!-- vue2 -->
<ChildComponent v-model="pageTitle" />
<!-- 简写: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
<script>
// ChildComponent.vue
export default {
model: {
prop: 'title',
event: 'change'
},
props: {
// 这将允许 `value` 属性用于其他用途
value: String,
// 使用 `title` 代替 `value` 作为 model 的 prop
title: {
type: String,
default: 'Default title'
}
}
}
</script>
<!-- vue3 -->
<ChildComponent v-model="pageTitle" />
<!-- 简写: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
<script>
// ChildComponent.vue
export default {
props: {
modelValue: String // 以前是`value:String`
},
methods: {
changePageTitle(title) {
this.$emit('update:modelValue', title) // 以前是 `this.$emit('input', title)`
}
}
}
</script>
vue3.0 项目结构
- reactivity:响应式系统
- runtime-core:与平台无关的运行时核心 (可以创建针对特定- 平台的运行时 - 自定义渲染器)
- runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
- runtime-test:用于测试
- server-renderer:用于服务器端渲染
- compiler-core:与平台无关的编译器核心
- compiler-dom: 针对浏览器的编译模块
- compiler-ssr: 针对服务端渲染的编译模块
- template-explorer:用于调试编译器输出的开发工具
- shared:多个包之间共享的内容
- vue:完整版本,包括运行时和编译器
1.Vue3.0-快速上手
创建Vue3的3种方式
Vite是Vue作者开发的一款意图取代webpack的工具
其实现原理是利用ES6的import会发送请求去加载文件的特性,
拦截这些请求, 做一些预编译, 省去webpack冗长的打包时间安装Vite
npm install -g create-vite-app利用Vite创建Vue3项目
create-vite-app projectName安装依赖运行项目
cd projectName
npm install
npm run dev
2.Vue3.0兼容Vue2.x
3.Vue3.0-Custom Renderer API
1 | - Webpack |
Vue3.0是如何变快的?
- diff算法优化: https://vue-next-template-explorer.netlify.app/
- Vue2中的虚拟dom是进行全量的对比
- Vue3新增了静态标记(PatchFlag),
在与上次虚拟节点进行对比时候,只对比带有patch flag的节点
并且可以通过flag的信息得知当前节点要对比的具体内容
- hoistStatic 静态提升
- Vue2中无论元素是否参与更新, 每次都会重新创建, 然后再渲染
- Vue3中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
- cacheHandlers 事件侦听器缓存
- 默认情况下onClick会被视为动态绑定, 所以每次都会去追踪它的变化
但是因为是同一个函数,所以没有追踪变化, 直接缓存起来复用即可
- 默认情况下onClick会被视为动态绑定, 所以每次都会去追踪它的变化
- ssr渲染
- 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,
即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dmo来渲染的快上很多很多。 - 当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,
这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。
- 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,
静态提升demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30<div>
<p>静态提升</p>
<p>静态提升</p>
<p>静态提升</p>
<p>{{msg}}}</p>
</div>
<script>
// 静态提升之前: -->
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "静态提升"),
_createVNode("p", null, "静态提升"),
_createVNode("p", null, "静态提升"),
_createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
]))
}
// 静态提升之后: -->
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "静态提升", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "静态提升", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "静态提升", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_hoisted_2,
_hoisted_3,
_createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
]))
}
</script>事件缓存demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<div>
<button @click="onClick">按钮</button>
</div>
<script>
// 开启事件监听缓存之前: -->
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"])
]))
}
// 开启事件监听缓存之后: -->
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)))
}, "按钮")
]))
}
// 注意点: 转换之后的代码, 大家可能还看不懂, 但是不要紧
// 我们只需要观察有没有静态标记即可
// 因为我们知道在Vue3的diff算法中, 只有有静态标记的才会进行比较, 才会进行追踪
</script>
附录: 虚拟 Dom PatchFlags
vue-next/packages/shared/src/patchFlags.ts
1 | export const enum PatchFlags { |
Tip
: Vue3 在 Virtual DOM 的优化上采用的就是inferno
的手段
渲染器 (render 函数)
vue-next/packages/runtime-core/src/renderer.ts
- 简易版render
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22function render(vnode, container) {
const prevVNode = container.vnode
if (prevVNode == null) {
if (vnode) {
// 没有旧的 VNode,只有新的 VNode。使用 `mount` 函数挂载全新的 VNode
mount(vnode, container)
// 将新的 VNode 添加到 container.vnode 属性下,这样下一次渲染时旧的 VNode 就存在了
container.vnode = vnode
}
} else {
if (vnode) {
// 有旧的 VNode,也有新的 VNode。则调用 `patch` 函数打补丁
patch(prevVNode, vnode, container)
// 更新 container.vnode
container.vnode = vnode
} else {
// 有旧的 VNode 但是没有新的 VNode,这说明应该移除 DOM,在浏览器中可以使用 removeChild 函数。
container.removeChild(prevVNode.el)
container.vnode = null
}
}
}
如果旧的 VNode 不存在且新的 VNode 存在,那就直接挂载(mount)新的 VNode ;如果旧的 VNode 存在且新的 VNode 不存在,那就直接将 DOM 移除;如果新旧 VNode 都存在,那就打补丁(patch):
它不仅仅是一个把 VNode 渲染成真实 DOM 的工具,它还负责以下工作:
控制部分组件生命周期钩子的调用
在整个渲染周期中包含了大量的 DOM 操作、组件的挂载、卸载,控制着组件的生命周期钩子调用的时机。多端渲染的桥梁 (createRenderer)
渲染器也是多端渲染的桥梁,自定义渲染器的本质就是把特定平台操作“DOM”的方法从核心算法中抽离,并提供可配置的方案。与异步渲染有直接关系(vue3 defineAsyncComponent)
Vue3 的异步渲染是基于调度器的实现,若要实现异步渲染,组件的挂载就不能同步进行,DOM的变更就要在合适的时机包含最核心的 Diff 算法
Diff 算法是渲染器的核心特性之一,可以说正是 Diff 算法的存在才使得 Virtual DOM 如此成功。
vue3.0 算法复杂度从O(n^3) -> O(n),创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升
mountElement
1 | - 简易版 mountElement |
它会调用浏览器提供的 document.createElement 函数创建元素,接着调用 appendChild 函数将元素添加到 container 中,但它具有以下缺陷:
- 1、VNode 被渲染为真实DOM之后,没有引用真实DOM元素
- 2、没有将 VNodeData 应用到真实DOM元素上
- 3、没有继续挂载子节点,即 children
- 4、不能严谨地处理 SVG 标签
所以vue 对它做了特殊处理
1 | function mountElement(vnode, container, isSVG) { |
Tree-Shaking的作用
Tree-shaking的本质是消除无用的js代码:在 webpack 项目中,当我们在入口文件中引入一个模块的时候,可能不会引入这个模块的所有代码,只引入了我们需要的代码,那么在使用webpack进行打包时,tree-shaking会自动帮我们把不用的代码过滤掉。
提升项目性能:JS绝大多数情况需要通过网络进行加载,然后执行,加载的文件越小,整体执行时间更短,所以去除无用代码以减少文件体积, 从而提升项目性能。
- 本文作者: Littleki
- 本文链接: https:/littleki.gitee.io/2020/10/21/分享会/Vue3.0-SHARE/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!