// vue3
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
...
})
// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不被覆盖
onMounted(() => {
...
})
</script>
// vue2
<script>
export default {
mounted() {
...
},
}
</script>
Vue2 | Vue3 |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
Vue2 响应式原理基础是Object.defineProperty
;Vue3 响应式原理基础是Proxy
。
Object.defineProperty
基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象。
Tips: writable
和value
与getter
和setter
不共存。
let obj = {}
let name = 'TJ.'
Object.defineProperty(obj, 'name', {
enumerable: true, // 可枚举(是否可通过for...in 或 Object.keys()进行访问)
configurable: true, // 可配置(是否可使用delete删除,是否可再次设置属性)
// value: '', // 任意类型的值,默认undefined
// writable: true, // 可重写
get: function() {
return name
},
set: function(value) {
name = value
}
})
Vue3 为什么会使用 Proxy
?
主要原因:无法监听对象或数组新增、删除的元素。
Vue2 方案:针对常用数组原型方法push
、pop
、shift
、unshift
、splice
、sort
、reverse
进行了hack处理;提供Vue.set
监听对象/数组新增属性。对象的新增/删除响应,还可以new
个新对象,新增则合并新属性和旧对象;删除则将删除属性后的对象深拷贝给新对象。
Proxy
Proxy
是ES6新特性,通过第2个参数handler
拦截目标对象的行为。相较于Object.defineProperty
提供语言全范围的响应能力,消除了局限性。但在兼容性上放弃了(IE11以下)
Vue2 是 选项式API(Option API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期函数等),导致代码的可读性变差,需要上下来回跳转文件位置。Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起。
除了增强了代码的可读性、内聚性,组合式API 还提供了较为完美的逻辑复用性方案,举个例子,如下所示公用鼠标坐标案例:
// main.vue
<template>
<span>mouse position {{x}} {{y}}</span>
</template>
<script setup>
import { ref } from 'vue'
import useMousePosition from './useMousePosition'
const {x, y} = useMousePosition()
}
</script>
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
let x = ref(0)
let y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return {
x,
y
}
}
</script>
Vue3 支持了多根节点组件,也就是fragment
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
Vue2 中编写页面的时候,我们需要去将组件包裹在<div>
中,否则报错。
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
Vue3 提供Teleport
组件可将部分DOM移动到 Vue app 之外的位置。比如项目中常见的弹框组件。
<button @click="dialogVisible = true">点击</button>
<teleport to="body">
<div class="dialog" v-if="dialogVisible">
</div>
</teleport>
Vue3 提供Suspense
组件,允许程序在等待异步组件时渲染兜底的内容,如loading
,使用户体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default
和fallback
。Suspense
确保加载完异步内容时显示默认插槽,并将fallback
插槽用作加载状态。
项目中踩过坑,若想在setup
中调用异步请求,需在setup
前加async
关键字。这时,会受到警告async setup() is used without a suspense boundary
。
解决方案:在父页面调用当前组件外包裹一层Suspense
组件。
Vue3 相比于 Vue2 虚拟DOM 上增加patchFlag
字段。我们借助Vue3 Template Explorer来看。
<div id="app">
<h1>技术摸鱼</h1>
<p>今天天气真不错</p>
<div>{{name}}</div>
</div>
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
const _withScopeId = n => (_pushScopeId("scope-id"),n=n(),_popScopeId(),n)
const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("h1", null, "技术摸鱼", -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("p", null, "今天天气真不错", -1 /* HOISTED */))
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", _hoisted_1, [
_hoisted_2,
_hoisted_3,
_createElementVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */)
]))
}
注意第 3 个_createElementVNode
的第 4 个参数即patchFlag
字段类型,字段类型情况如下所示。1 代表节点为动态文本节点,那在diff
过程中,只需比对文本对容,无需关注class、style
等。除此之外,发现所有的静态节点,都保存为一个变量进行静态提升,可在重新渲染时直接引用,无需重新创建。
export const enum PatchFlags {
TEXT = 1, // 动态文本内容
CLASS = 1 << 1, // 动态类名
STYLE = 1 << 2, // 动态样式
PROPS = 1 << 3, // 动态属性,不包含类名和样式
FULL_PROPS = 1 << 4, // 具有动态 key 属性,当 key 改变,需要进行完整的 diff 比较
HYDRATE_EVENTS = 1 << 5, // 带有监听事件的节点
STABLE_FRAGMENT = 1 << 6, // 不会改变子节点顺序的 fragment
KEYED_FRAGMENT = 1 << 7, // 带有 key 属性的 fragment 或部分子节点
UNKEYED_FRAGMENT = 1 << 8, // 子节点没有 key 的fragment
NEED_PATCH = 1 << 9, // 只会进行非 props 的比较
DYNAMIC_SLOTS = 1 << 10, // 动态的插槽
HOISTED = -1, // 静态节点,diff阶段忽略其子节点
BAIL = -2 // 代表 diff 应该结束
}
Vue3 的cacheHandler
可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数。加一个click
事件。
<div id="app">
<h1>小丽SPA</h1>
<p>今天天气好晴朗</p>
<div>{{name}}</div>
<span onCLick="() => {}"><span>
</div>
渲染函数如下:
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
const _withScopeId = n => (_pushScopeId("scope-id"),n=n(),_popScopeId(),n)
const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("h1", null, "小丽SPA", -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("p", null, "今天天气好晴朗", -1 /* HOISTED */))
const _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("span", { onCLick: "() => {}" }, [
/*#__PURE__*/_createElementVNode("span")
], -1 /* HOISTED */))
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", _hoisted_1, [
_hoisted_2,
_hoisted_3,
_createElementVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */),
_hoisted_4
]))
}
Vue3 由TS重写,相对于 Vue2 有更好地 TypeScript 支持。
- Vue2
Option API
中option
是个简单对象,而TS是一种类型系统,面向对象的语法,不是特别匹配。 - Vue2 需要
vue-class-component
强化vue原生组件,也需要vue-property-decorator
增加更多结合 Vue 特性的装饰器,写法比较繁琐。