Vue3 - Composition API
一、常用 Composition API
1、拉开序幕的 setup
1. 理解: Vue3.0 中一个新的配置项,值为一个函数。
2. setup 是所有 Composition API (组合 API)“表演的舞台”。
3. 组件中所用到的:数据、方法等,均要配置在 setup 中。
4. setup 函数的两种返回值:
1. 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点)
2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
5. 注意点:
1. 尽量不要与 Vue2.x 配置混用
1. Vue2.x配置(data、methods、computed...)中可以访问到 setup 中的属性、方法。
2. 但在 setup 中不能访问到 Vue2.x配置(data、methods、computed...)。
3. 如果有重名,setup 优先。
2. setup 不能是一个异步函数,因为返回值不再是 return 的对象,而是 promise,模板看不到 return 对象中的属性。
———— App.vue
<template>
<h1>基本信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<button @click="sayHello">打招呼</button>
<br>
<br>
<button @click="test">vue2获取vue3的数据</button>
<br>
<br>
<button @click="test1">vue3获取vue2的数据</button>
</template>
<script>
// import { h } from 'vue' // h —— vue的渲染函数,传入两个参数:(dom标签,内容)
export default {
name: 'App',
// vue2.x
data() {
return {
sex: '男',
}
},
methods: {
test() {
alert(`vue2获取vue3的数据:
姓名:${this.name}
年龄:${this.age}
打招呼:${this.sayHello}
`)
}
},
// vue3.x 简单测试 setup,暂时不做响应式
setup() {
// 数据
let name = "许三多"
let age = 23
// 方法
function sayHello() {
alert(`你好!我叫${name},今年${age}岁`)
}
function test1() {
alert(`vue3获取vue2的数据:
性别:${this.sex}
`)
}
// 返回一个对象(常用)
return {
name,
age,
sayHello,
test1,
}
// 返回一个渲染函数
// return () => {
// h('h1', '我是渲染函数')
// }
}
}
</script>
2. ref 函数
- 作用: 定义一个响应式的数据
- 语法: const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象)
- JS中操作数据:xxx.value
- 模板中读取数据:不需要 .value,直接:<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型,也可以是对象类型
- 基本类型的数据:响应式依然是靠 Object.defineProperty() 的 get 与 set 完成的
- 对象类型的数据:内部 ”求助“了 Vue 3.0 中的一个新函数 —— reactive 函数。
2.1. 传入基本类型的数据
<template>
<h1>基本信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeInfo">改变信息</button>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
// 简单测试 setup,暂时不做响应式
setup() {
// 数据
let name = ref("许三多")
let age = ref(23)
// 方法
function changeInfo() {
name.value = '张三'
age.value = 18
// console.log(name, age)
}
// 返回一个对象(常用)
return {
name,
age,
changeInfo,
}
}
}
</script>
2.2. 传入对象类型的数据
<template>
<h1>基本信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>工作类型:{{job.type}}</h2>
<h2>薪水:{{job.salary}}</h2>
<button @click="changeInfo">改变信息</button>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
// 简单测试 setup,暂时不做响应式
setup() {
// 数据
let name = ref("许三多")
let age = ref(23)
let job = ref({
type: '攻城狮',
salary: '30k'
})
// 方法
function changeInfo() {
name.value = '张三'
age.value = 18
job.value.type = '鼓励师'
job.value.salary = '50k'
// console.log(name, age)
}
// 返回一个对象(常用)
return {
name,
age,
changeInfo,
job,
}
}
}
</script>
3、reactive 函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用 ref 函数)
- 语法: const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(proxy对象)
- reactive 定义的响应式数据是 "深层次的"
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
<template>
<h1>基本信息</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>工作类型:{{person.job.type}}</h2>
<h2>薪水:{{person.job.salary}}</h2>
<h2>测试的数据c:{{person.job.a.b.c}}</h2>
<h2>爱好:{{person.hobby}}</h2>
<button @click="changeInfo">改变信息</button>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
// 简单测试 setup,暂时不做响应式
setup() {
// 数据
let person = reactive({
name: '张三',
age: 18,
job: {
type: '攻城狮',
salary: '30k',
a: {
b: {
c: 666
}
}
},
hobby: ["抽烟", "喝酒", "烫头"]
})
// 方法
function changeInfo() {
person.name = '李四'
person.age = 88
// console.log(job)
person.job.type = '鼓励师'
person.job.salary = '50k'
person.job.a.b.c = 999
person.hobby[0] = "学习"
}
// 返回一个对象(常用)
return {
person,
changeInfo,
}
}
}
</script>
4、Vue 3.0 中的响应式原理
vue 2.x 的响应式
- 实现原理:
- 对象类型:通过 Object.defineProperty() 对属性的 读取 / 修改 进行拦截 (数据劫持)。
- 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', {
get() {},
set() {},
});
- 存在问题:
- 新增属性/删除属性,界面不会更新。
- 直接通过下标改变数组,界面不会自动更新。
vue 3.0 的响应式
- 实现原理:
- 通过 Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写 / 属性的添加 / 属性的删除等。
- 通过 Reflect (反射):对被代理对象的属性进行操作。
- MDN 文档中描述的 Proxy 与 Reflect:
- Proxy:https://developer.mozila.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
- Reflect:https://developer.mozila.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
const p = new Proxy(person, {
// 拦截读取属性值
get(target, prop) {
return Reflect.get(target, prop);
},
// 拦截设置属性值或添加新属性
set(target, prop, value) {
Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty(target, prop) {
return Reflect.delete(target, prop);
}
})
proxy.name = 'tom'
<script>
const person = {
name: '张三',
age: 18
};
// 模拟 vue2 的响应式
//#region
// let p = {}
// Object.defineProperty(p, 'name', {
// configurable: true,
// get() {
// console.log(`有人读取person的name属性`)
// return person.name
// },
// set(value) {
// console.log(`有人修改person的name属性`,value)
// person.name = value
// }
// });
// Object.defineProperty(p, 'age', {
// configurable: true,
// get() {
// console.log(`有人读取person的age属性`)
// return person.age
// },
// set(value) {
// console.log(`有人修改person的age属性`,value)
// person.age = value
// }
// });
//#endregion</script>
5、reactive 对比 ref
- 从定义数据角度对比:
- ref 用来定义:基本类型数据
- reactive 用来定义:对象或数组类型数据
- 备注:ref 也可以用来定义 对象或数组类型数据,它内部会自动通过 reactive 转为 代理对象
- 从原理角度对比:
- ref 通过 Object.defineProperty() 的 get 与 set 来实现响应式(数据劫持)
- reactive 通过使用 Proxy 来实现响应式(数据劫持),并通过 Reflect 操作 源对象 内部的数据
- 从使用角度对比:
- ref 定义的数据:操作数据需要 .value,读取数据时模板中直接读取不需要 .value
- reactive 定义的数据:操作数据与读取数据:均不需要 .value
6、setup 的两个注意点:
- setup 执行的时机:
- 在 beforeCreate 之前执行一次, this是 undefined
- setup 的参数
- props:值为对象,包含:组件外部传递过来,且组件内部生命接收了的属性
- context:上下文对象
- attrs:值为对象,包含:组件外部传递过来,但没有在 props 配置中声明的属性,相当于 this.$attrs
- slots:收到的插槽内容,相当于 this.$slots
- emit:分发自定义事件的函数,相当于 this.$emit
7、计算属性与监视
computed 函数
- 与 Vue2.x 中 computed 配置功能一致
- 写法:
import { computed } from 'vue
setup() {
// 计算属性简写
let fullName = computed(() => {
return person.firstName + '-' + person.lastName
})
// 计算属性 - 完整
let fullName = computed({
get() {
return person.firstName + '-' + person.lastName
},
set(value) {
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
watch 函数
- 与 Vue2.x 中的 watch 配置功能一致
- 两个小 '坑':
监视 reactive 定义的响应式数据时:oldValue 无法正确获取,强制开启了深度监视(deep配置失效)。
监视 reactive 定义的响应式数据中某个属性时:deep 配置有效。
// 情况一:监视 ref 定义的响应式数据
watch(sum, (newValue, oldValue) => {
console.log('sum变化了', newValue, oldValue)
}, {immediate:true})
// 情况二:监视多个ref 定义的响应式数据
watch([sum, msg], (newValue, oldValue) => {
console.log('sum或msg变化了', newValue, oldValue)
})
/* 情况三: 监视 reactive 定义的响应式数据
若 watch 监视的是 reactive 定义的响应式数据,则无法正确获得 oldValue,并且强制开启了深度监视 */
watch(person, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue)
}, {immediate:true, deep:false}) // 此处的deep配置不再生效
// 情况四:监视 reactive 所定义的一个响应式数据中的某个属性
watch(() => person.name, (newValue, oldValue) => {
console.log('person的name 变化了', newValue, oldValue)
})
// 情况五:监视reactive所定义的一个响应式数据中的某些属性
watch([() => person.name, ()=>person.age], (newValue, oldValue) => {
console.log('person的name或age变化了', newValue, oldValue)
})
// 特殊情况
watch(() => person.job, (newValue, oldValue) => {
console.log('person的job变化了',newValue, oldValue)
}, {deep:true}) // 由于监视的是reactive所定义的对象中的某个属性,所以deep配置有效
watchEffect 函数
- watch 的套路是:既要指明监视的属性,也要指明监视的回调。
- watchEffect 的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- watchEffect有点像computed:
- 但computed 注重的是计算出来的值(回调函数的返回值),所以必须要写返回值
- 而 watchEffect 更注重的是过程(回调函数的函数体),所以不用写返回值
// watchEffect 所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(() => {
const x1 = sum.value
const x2 = person.age
console.log('watchEffect 配置的回调执行了')
})
8、生命周期
setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有两个名字变了:
beforeDestroy 改名为 beforeUnmount
destroyed 改名为 unmounted
Vue3.0 也提供了Composition API 形式的生命周期钩子,与Vue2.x 中钩子对应关系如下:
beforeCreate ==> setup()
created ==> setup()
beforeMount ==> onBeforeMount
mounted ==> onMounted
beforeUpdate ==> onBeforeUpdate
updated ==> onUpdated
beforeUnmount ==> onBeforeUnmount
unmounted ==> onUnmounted
9、自定义hook函数
1、什么是hook?
本质是一个函数,把 setup 函数中使用的 Composition API 进行了封装
2、类似于 vue2.x 中的mixin
3、自定义 hook 的优势:复用代码,让 setup 中的逻辑更清楚易懂。
10、toRef
1、作用:创建一个 ref 对象,其 value 值指向另一个对象中的某个属性值
2、语法:
const name = toRef(person, 'name')
const salary = toRef(person.job.j1, 'salary')
const p = toRefs(person)
return {
name,
salary,
...p
}
3、应用:要将响应式对象中的某个属性单独提供给外部使用时
4、扩展:toRefs 与 toRef 功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
二、其他Composition API
1、shallowReactive 与 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
- 什么时候使用?
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化 ==> shallowReactive
- 如果有一个对象数据,后续功能不会修改改对象中的属性,而是生成新的对象来替换 ==> shallowRef
2、readonly 与 shallowReadonly
- readonly:让一个响应式数据变成只读的(深只读)
- shallowReadonly:让一个响应式数据变为只读的(浅只读)
- 应用场景:不希望数据被修改时
3、toRaw 与 markRaw
- toRaw:
- 作用:将一个由 reactive 生成的 响应式对象转为 普通对象
- 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象
- 应用场景:
- 1、有些值不应该被设置为响应式的,例如复杂的第三方类库等
- 2、当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
4、provide 与 inject
- 作用:实现祖孙组件间通信
- 套路:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据
- 具体写法:
- 1、祖组件中:
setup() {
let car = reactive({name: '奔驰', price: '40万'})
provide('car', car)
}
-
- 2、后代组件中:
setup(props, context) {
const car = inject('car')
return {car}
}
5、响应式数据的判断
- isRef:检查一个值是否为一个 ref 对象
- isReactive:检查一个对象是否是由 reactive 创建的响应式代理
- isReadonly:检查一个对象是否是由 readonly 创建的只读代理
- isProxy:检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
三、Composition API的优势
1、 Options API 存在的问题
使用传统 OptionsAPI 中,新增或者修改一个需求,就需要分别在 data,methods,computed 里修改
2、Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起
四、新的组件
1、Fragment
- 在Vue2中:组件必须有一个根标签
- 在Vue3中:组件可以没有根标签,内部会将多个标签 包含在一个Fragment 虚拟元素中
- 好处:减少标签层级,减小内存占用
2、Teleport
什么是Teleport? 是一种能够将我们的组件html结构移动到指定位置的技术
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow=false">关闭弹窗</button>
</div>
</div>
</teleport>