Composition API
1) setup
- 新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
- 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
2) ref
- 作用: 定义一个数据的响应式
- 语法: const xxx = ref(initValue):
- 创建一个包含响应式数据的引用(reference)对象
- js中操作数据: xxx.value
- 模板中操作数据: 不需要.value
- 一般用来定义一个基本类型的响应式数据
3) reactive
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
4) 比较Vue2与Vue3的响应式(重要)
vue2的响应式
-
核心:
- 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
- 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
-
问题
- 对象直接新添加的属性或删除已有属性, 界面不会自动更新
- 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
Vue3的响应式
-
核心:
-
通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等...
-
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
-
文档:
-
5) setup细节
-
setup执行的时机
- 在beforeCreate之前执行(一次), 此时组件对象还没有创建
- this是undefined, 不能通过this来访问data/computed/methods / props
- 其实所有的composition API相关回调函数中也都不可以
-
setup的返回值
- 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
- 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
- 返回对象中的方法会与methods中的方法合并成功组件对象的方法
- 如果有重名, setup优先
- 注意:
- 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
- setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
-
setup的参数
- setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含props配置声明且传入了的所有属性的对象
- attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
6) reactive与ref-细节
-
是Vue3的 composition API中2个最重要的响应式API
-
ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
-
如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
-
ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
-
reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
-
ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
7) 计算属性与监视
-
computed函数:
- 与computed配置功能一致
- 只有getter
- 有getter和setter
-
watch函数
- 与watch配置功能一致
- 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
- 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
- 通过配置deep为true, 来指定深度监视
-
watchEffect函数
- 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次, 从而可以收集需要监视的数据
- 监视数据发生变化时回调
1) shallowReactive 与 shallowRef
-
shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
-
shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
-
什么时候用浅响应式呢?
- 一般情况下使用ref和reactive即可
- 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
- 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
2) readonly 与 shallowReadonly
-
readonly:
- 深度只读数据
- 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
-
shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
-
应用场景:
- 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
readonly: 深度只读数据 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。 只读代理是深层的:访问的任何嵌套 property 也是只读的。 shallowReadonly: 浅只读数据 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 应用场景: 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
3) toRaw 与 markRaw
-
toRaw
- 返回由
reactive或readonly方法转换成响应式代理的普通对象。 - 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- 返回由
-
markRaw
-
标记一个对象,使其永远不会转换为代理。返回对象本身
-
应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
-
<template>
<h2>{{state}}</h2>
<button @click="testToRaw">测试toRaw</button>
<button @click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
/*
toRaw: 得到reactive代理对象的目标数据对象
*/
import {
markRaw,
reactive, toRaw,
} from 'vue'
export default {
setup () {
const state = reactive<any>({
name: 'tom',
age: 25,
})
const testToRaw = () => {
const user = toRaw(state)
user.age++ // 界面不会更新
}
const testMarkRaw = () => {
const likes = ['a', 'b']
// state.likes = likes
state.likes = markRaw(likes) // likes数组就不再是响应式的了
setTimeout(() => {
state.likes[0] += '--'
}, 1000)
}
return {
state,
testToRaw,
testMarkRaw,
}
}
}
</script>
4) toRef
- 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
- 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
- 应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用
toRef: 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响 应用: 当要将某个 prop 的 ref 传递给复合函数时,toRef 很有用
5) customRef
- 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
<template>
<h2>App</h2>
<input v-model="keyword" placeholder="搜索关键字"/>
<p>{{keyword}}</p>
</template>
<script lang="ts">
/*
customRef:
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
需求:
使用 customRef 实现 debounce 的示例
*/
import {
ref,
customRef
} from 'vue'
export default {
setup () {
const keyword = useDebouncedRef('', 500)
console.log(keyword)
return {
keyword
}
},
}
/*
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: number
return customRef((track, trigger) => {
return {
get() {
// 告诉Vue追踪数据
track()
return value
},
set(newValue: T) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
// 告诉Vue去触发界面更新
trigger()
}, delay)
}
}
})
}
</script>
6) provide 与 inject
- provide
和inject提供依赖注入,功能类似 2.x 的provide/inject - 实现跨层级组件(祖孙)间通信
7) 响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly创建的只读代理 - isProxy: 检查一个对象是否是由
reactive或者readonly方法创建的代理