vue3 正式推出至今,对其更新了解一直比较零散, 于是打算完整的了解一下,
然后其中比较重要的内容整理成了这份文档,
该文档只做精简整理,不做深入
proxy
为了更好了拦截对象/数组的变化, vue的双向绑定底层实现也是放弃了 object.defineProperty 选择了 proxy
object.defineProperty 的弊端就是无法监听对象的新增字段,vue初始化的时候还需要花费很多时间去递归对对象的每个属性挂载, 所以导致了 vue2 的各种限制需要使用 $set 取设置新增的, 并且需要取 hack 掉数组的push、replace等等方法,所以 vue3 也是选择了更好用的 proxy
全局方法
// Vue 2.x
Vue.prototype.$http = () => {}
// Vue 3
const app = createApp({})
app.config.globalProperties.$http = () => {}
composition api
原本 vue2 中对象上的各种属性在 vue3 中被抽离出来,通过 composition api 形式提供开发者使用,因此能更好的支持更好的 tree sharking,即打包时未使用的api 不会被打入
ref
包装基本类型使用, 当然也能传入对象(内部调用 reactive)
修改ref的值需要通过 .value
import { ref } from 'vue'
const count = ref(2)
// 修改
count.value = 3
reactive
用于引用类型, 和 ref 不同的是不需要通过 .value
import { reactive } from 'vue'
const person = reactive({ name: '张三', age: 18 })
// 修改
person.name = '李四'
isRef
判断某个值是否是 ref 创建出来的
import { ref, isRef } from 'vue'
const count = ref(2)
isRef(count) // true
toRef
把 reactive 的某个值都处理成 ref, 会保持对其原本 reactive 的响应式连接
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
toRefs
把 reactive 的每个值都处理成 ref, 多是为了更方便的在模版中使用 reactive中的值时使用
export default {
setup() {
// 可以在不失去响应性的情况下解构
const state = reactive({
foo: 1,
bar: 2
})
return {
...toRefs(state)
}
}
}
isReactive
看的出来, 判断是否是 reactive
readonly
接收一个 ref 或者 reactive, 返回一个只读的响应式新对象
const original = reactive({ count: 0 })
const copy = readonly(original)
copy.count++ // 警告!
watchEffect
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
watch
与 watchEffect 相比,watch 允许我们:
- 惰性地执行副作用;
- 更具体地说明应触发侦听器重新运行的状态;
- 访问被侦听状态的先前值和当前值。
// 官网 demo
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(() => state.count, (count, prevCount) => {
/* ... */
}
)
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
// 侦听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
computed
const count = ref(1)
const plusOne = computed(() => count.value + 1)
// 自定义 get set
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
filter
不再支持过滤器, 建议用方法调用或计算属性替换
// 官网 demo
<template>
<p>{{ accountInUSD }}</p>
</template>
<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
computed: {
accountInUSD() {
return '$' + this.accountBalance
}
}
}
</script>
当然也可以使用 js 原生的管道运算符
// 相当于是 plus(123)
<template>
<p>{{ 123 |> plus }}</p>
</template>
<script>
export default {
setup() {
function plus(val) {
return val + 100
}
return {
myFormat
}
}
}
</script>
生命周期
- beforeCreate -> setup
- created -> setup
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeUnmount -> onBeforeUnmount
- unmounted -> onUnmounted
- errorCaptured -> onErrorCaptured
teleport
传送门, 将该组件内部的dom 渲染到指定的容器内。
使用场景:modal 弹窗终于不用在组件内部弹出来了
to 参数接收一个指定 dom 的 selector, 并支持多个同时使用,他会追加进去,一起出现
<div id="modal"></div>
<teleport to="#modal" >
<div>a</div>
</teleport>
<teleport to="#modal" >
<div>b</div>
</teleport>
// 结果
<div id="modal">
<div>a</div>
<div>b</div>
</div>
css 深度选择器
:deep()
<style scoped>
.a :deep(.b) {
/* ... */
}
</style>
上面的代码会被编译成:
.a[data-v-f3f3eg9] .b {
/* ... */
}
css v-bind
单文件组件的 <style> 标签可以通过 v-bind 这一 CSS 函数将 CSS 的值关联到动态的组件状态上:
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style>
.text {
color: v-bind(color);
}
</style>
这个语法同样也适用于 <script setup>,且支持 JavaScript 表达式 (需要用引号包裹起来)
<script setup>
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
style module
<style module> 标签会被编译为 CSS Modules 并且将生成的 CSS 类作为 $style 对象的键暴露给组件:
<template>
<p :class="$style.red">
This should be red
</p>
</template>
<style module>
.red {
color: red;
}
</style>
Ref 数组
在 Vue 2 中,在 v-for 中使用的 ref attribute 会用 ref 数组填充相应的 $refs property。当存在嵌套的 v-for 时,这种行为会变得不明确且效率低下。
在 Vue 3 中,此类用法将不再自动创建 $ref 数组。要从单个绑定获取多个 ref,请将 ref 绑定到一个更灵活的函数上 (这是一个新特性):
<div v-for="item in list" :ref="setItemRef"></div>
export default {
data() {
return {
itemRefs: []
}
},
methods: {
setItemRef(el) {
if (el) {
this.itemRefs.push(el)
}
}
},
beforeUpdate() {
this.itemRefs = []
},
updated() {
console.log(this.itemRefs)
}
}
结合组合式 API:
import { onBeforeUpdate, onUpdated } from 'vue'
export default {
setup() {
let itemRefs = []
const setItemRef = el => {
if (el) {
itemRefs.push(el)
}
}
onBeforeUpdate(() => {
itemRefs = []
})
onUpdated(() => {
console.log(itemRefs)
})
return {
setItemRef
}
}
}
fragment
允许多个根节点
<template>
<a>...</a>
<b>...</b>
<c>...</c>
</template>
data 选项
vue2: data 可以是 object 或者是 function
vue3: 已标准化为只接受返回 object 的 function
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
Mixin 合并行为变更
当来自组件的 data() 及其 mixin 或 extends 基类被合并时,合并操作现在将被浅层次地执行:
const Mixin = {
data() {
return {
user: {
name: 'Jack',
id: 1
}
}
}
}
const CompA = {
mixins: [Mixin],
data() {
return {
user: {
id: 2
}
}
}
}
在 Vue 2.x 中,生成的 $data 是:
{
"user": {
"id": 2,
"name": "Jack"
}
}
在 3.0 中,其结果将会是:
{
"user": {
"id": 2
}
}
移除$listeners
在 Vue 3 中,事件监听器被认为是只是以 on 为前缀的 attribute,这样它就成为了 listeners 被移除了。
emits
需要使用 emits 记录每个组件所触发的所有事件。 vue3 中移除了 .native 修饰符。任何未在 emits 中声明的事件监听器都会被算入组件的 $attrs,并将默认绑定到组件的根节点上。
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text'],
emits: ['accepted']
}
</script>
v-on.native
对于子组件中未被定义为组件触发的所有事件监听器,Vue 现在将把它们作为原生事件监听器添加到子组件的根元素中 (除非在子组件的选项中设置了 inheritAttrs: false)。
拿click 事件举例,现在想要 @click.native 效果, 子组件的 emits 里不写 'click' 就行
v-model
支持多个 v-model,可自定义props 值接收
// 父组件
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- 是以下的简写: -->
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
// 子组件
// ChildComponent.vue
export default {
props: {
modelValue: String
},
emits: ['update:modelValue'],
methods: {
changePageTitle(title) {
this.$emit('update:modelValue', title) // 以前是 `this.$emit('input', title)`
}
}
}
总结:
接收: 直接props 定义就行, 比如: name、 age
提交: update: 开头, emit('update:name', value), emit('update:age', value)
外部使用: v-model:name="xxx" v-model:age="aaa"
事件 API
off 和 $once 实例方法已被移除,组件实例不再实现事件触发接口。
自定义指令
目标事件和生命周期统一
created - 新增!在元素的 attribute 或事件监听器被应用之前调用。
bind → beforeMount
inserted → mounted
beforeUpdate:新增!在元素本身被更新之前调用,与组件的生命周期钩子十分相似。
update → 移除!该钩子与 updated 有太多相似之处,因此它是多余的。请改用 updated。
componentUpdated → updated
beforeUnmount:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。
unbind -> unmounted
异步组件
在 Vue 3 中,由于函数式组件被定义为纯函数,因此异步组件需要通过将其包裹在新的 defineAsyncComponent 助手方法中来显式地定义:
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// 不带选项的异步组件
const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
// 带选项的异步组件
const asyncModalWithOptions = defineAsyncComponent({
loader: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
Suspense
组件内的异步组件处理方案
default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点。
<template>
<suspense>
<template #default>
<todo-list />
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</suspense>
</template>
<script>
export default {
components: {
TodoList: defineAsyncComponent(() => import('./TodoList.vue'))
}
}
</script>
default 插槽放你原本的组件
fallback 插槽放组件未加载好时的 loading
渲染函数 API
vue2 里面是在 render(h){} 函数里接收的 h 函数, 当我们需要复用方法时需要将 h 传来传去, 很不方便
vue3 h 函数被抽离到 'vue' 包里面了, render 函数内不再接收
import { h, reactive } from 'vue'
v-bind 合并行为
在 2.x 中,如果一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么这个独立 attribute 总是会覆盖 object 中的绑定。
<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="red"></div>
在 3.x 中,如果一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么绑定的声明顺序将决定它们如何被合并。换句话说,相对于假设开发者总是希望独立 attribute 覆盖 object 中定义的内容,现在开发者能够对自己所希望的合并行为做更好的控制。
<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="blue"></div>
<!-- 模板 -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- 结果 -->
<div id="red"></div>
VNode 生命周期事件
// vue2
<template>
<child-component @hook:updated="onUpdated">
</template>
// vue3
<template>
<child-component @vnode-updated="onUpdated">
</template>
// 驼峰
<template>
<child-component @vnodeUpdated="onUpdated">
</template>
生命周期事件监听方式,从 @hook: 开头改为 @vnode- 开头
过渡的 class 名更改
vue2
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
vue3 重命名为
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
在 prop 的默认函数中访问
生成 prop 默认值的工厂函数不再能访问 this。
取而代之的是:
组件接收到的原始 prop 将作为参数传递给默认函数;
inject API 可以在默认函数中使用。
import { inject } from 'vue'
export default {
props: {
theme: {
default (props) {
// `props` 是传递给组件的、
// 在任何类型/默认强制转换之前的原始值,
// 也可以使用 `inject` 来访问注入的 property
return inject('theme', 'default-theme')
}
}
}
}
侦听数组
当使用 watch 选项侦听数组时,只有在数组被替换时才会触发回调。换句话说,在数组被改变时侦听回调将不再被触发。要想在数组被改变时触发侦听回调,必须指定 deep 选项。
{
watch: {
bookList: {
handler(val, oldVal) {
console.log('book list changed')
},
deep: true
}
}
}