Vue3 Composition API
什么是组合式 API?
通过创建 Vue 组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要。
假设在我们的应用程序中,我们有一个视图来显示某个用户的仓库列表。除此之外,我们还希望应用搜索和筛选功能。处理此视图的组件可能如下所示:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: ‘‘ // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: ‘getUserRepositories‘ // 1
},
methods: {
getUserRepositories () {
// 使用 `this.user` 获取用户仓库
}, // 1
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
该组件有以下几个职责:
- 从假定的外部 API 获取该用户名的仓库,并在用户更改时刷新它
- 使用
searchQuery
字符串搜索存储库 - 使用
filters
对象筛选仓库
用组件的选项 (data
、computed
、methods
、watch
) 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,尤其是对于那些一开始就没有编写这些组件的人来说。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果我们能够将与同一个逻辑关注点相关的代码配置在一起会更好。而这正是组合式 API 使我们能够做到的。
为什么需要Vue对组合API
随着Vue的日益普及,人们也开始在大型和企业级应用程序中采用Vue。 由于这种情况,在很多情况下,此类应用程序的组件会随着时间的推移而逐渐增长,并且有时使用单文件组件人们很难阅读和维护。 因此,需要以逻辑方式制动组件,而使用Vue的现有API则不可能。
代替添加另一个新概念,提议使用Composition API,该API基本将Vue的核心功能(如创建响应式数据)公开为独立功能,并且该API有助于在多个组件之间编写简洁且可复用的代码。
Vue已有的替代方案缺点是什么?
在引入新的API之前,Vue还有其他替代方案,它们提供了诸如mixin,HOC(高阶组件),作用域插槽之类的组件之间的可复用性,但是所有方法都有其自身的缺点,由于它们未被广泛使用。
- Mixins:一旦应用程序包含一定数量的mixins,就很难维护。 开发人员需要访问每个mixin,以查看数据来自哪个mixin。
- HOC:这种模式不适用于.vue 单文件组件,因此在Vue开发人员中不被广泛推荐或流行。
- 作用域插槽一通用的内容会进行封装到组件中,但是开发人员最终拥有许多不可复用的内容,并在组件模板中放置了越来越多的插槽,导致数据来源不明确。
简而言之,组合API有助于
- 由于API是基于函数的,因此可以有效地组织和编写可重用的代码。
- 通过将共享逻辑分离为功能来提高代码的可读性。
- 实现代码分离。
- 在Vue应用程序中更好地使用TypeScript。
组合API(常用部分)
setup
- 新的 option, 所有的组合 API 函数都在此使用,只在初始化时执行一次
- 函数如果返回对象,对象中的属性或方法,模板中可以直接使用
哈哈 我又变帅了
{{number}}
ref
作用:定义一个数据的响应式
语法:const xxx = ref(initValue):
- 创建一个包含响应式数据的引用(reference)对象
- js/ts 中操作数据:xxx.value
- HTML模板中操作数据:不需要.value
一般用来定义一个基本类型的响应式数据
Vue2写法
setup和ref的基本使用
{{ count }}
Vue3写法实现同一功能
setup和ref的基本使用
{{ count }}
reactive
作用:定义多个数据的响应式
const proxy = reactive(obj)
:接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
reactive的使用
名字:{{user.name}}
年龄:{{user.age}}
媳妇:{{user.wife}}
性别:{{user.gender}}
比较 Vue2 与 Vue3 的响应式
Vue2 的响应式
核心:
- 对象: 通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦截)
- 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, ‘count‘, {
get() {},
set() {}
})
问题
- 对象直接新添加的属性或删除已有属性,界面不会自动更新
- 直接通过下标替换元素或更新 length,界面不会自动更新 arr[1] = {}
Vue3 的响应式
核心:
- 通过 Proxy(代理):拦截对 data 任意属性的任意(13 种)操作,包括属性值的读写、属性的添加、属性的删除等……
- 通过 Reflect(反射):动态对被代理对象的相应属性进行特定的操作
- 文档:
new Proxy(data, {
// 拦截读取属性值
get(target, prop) {
return Reflect.get(target, prop);
},
// 拦截设置属性值或添加新属性
set(target, prop, value) {
return Reflect.set(target, prop, value);
},
// 拦截删除属性
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop);
},
});
proxy.name = ‘tom‘;
Title
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
App父级组件
msg: {{ msg }}
Child子级组件
msg: {{ msg }}
count: {{ count }}
reactive 与 ref细节
是 Vue3 的 composition API 中 2 个最重要的响应式 API
ref 用来处理基本类型数据,reactive 用来处理对象(递归深度响应式)
如果用 ref 对象/数组,内部会自动将对象/数组转换为 reactive 的代理对象
ref 内部:通过给 value 属性添加 getter/setter 来实现对数据的劫持
reactive 内部:通过使用 Proxy 来实现对对象内部所有数据的劫持,并通过 Reflect 操作对象内部数据
ref 的数据操作:在 js 中要.value,在模板中不需要(内部解析模板时会自动添加.value)
ref和reactive更新数据的问题
m1: {{ m1 }}
m2: {{ m2 }}
m3: {{ m3 }}
计算属性与监视
computed 函数:
- 与 computed 配置功能一致
- 只有 getter
- 有 getter 和 setter
watch 函数
- 与 watch 配置功能一致
- 监视指定的一个或多个响应式数据,一旦数据变化,就自动执行监视回调
- 默认初始时不执行回调,但可以通过配置 immediate 为 true,来指定初始时立即执行第一次
- 通过配置 deep 为 true,来指定深度监视
watchEffect 函数
- 不用直接指定要监视的数据,回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次,从而可以收集需要监视的数据
- 监视数据发生变化时回调
计算属性和监视
计算属性和监视
生命周期
Vue2.x 的生命周期
与 2.x 版本生命周期相对应的组合式 API
beforeCreate
-> 使用setup()
created
-> 使用setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
新增的钩子函数
组合式 API 还提供了以下调试钩子函数:
- onRenderTracked
- onRenderTriggered
App父级组件
Child子级组件
msg: {{ msg }}
自定义 hook 函数
- 使用 Vue3 的组合 API 封装的可复用的功能函数
- 自定义 hook 的作用类似于 vue2 中的 mixin 技术
- 自定义 Hook 的优势:很清楚复用功能代码的来源, 更清楚易懂
需求1
收集用户鼠标点击的页面坐标
自定义hook函数操作
x:{{ x }}, y: {{ y }}
import { onBeforeUnmount, onMounted, ref } from ‘vue‘;
export default function () {
const x = ref(-1);
const y = ref(-1);
// 点击事件的回调函数
const clickHandler = (event:MouseEvent) => {
x.value = event.pageX;
y.value = event.pageY;
};
// 页面接在完毕了,再进行点击的操作
// 页面接在完毕的生命周期
onMounted(() => {
window.addEventListener(‘click‘, clickHandler);
});
onBeforeUnmount(() => {
window.removeEventListener(‘click‘, clickHandler);
});
return {
x, y,
};
}
需求2
封装发 ajax 请求的 hook 函数
利用 TS 泛型强化类型检查
自定义hook函数操作
x:{{ x }}, y: {{ y }}
正在加载中
错误信息:{{ errorMsg }}
- firstName: {{ item.firstName }}
- lastName: {{ item.lastName }}
- email: {{ item.email }}
// 引入axios
// 发送ajax请求
import { ref } from ‘vue‘;
import axios from ‘axios‘;
export default function (url: string) {
// 加载的状态
const loading = ref(true);
const data = ref(null);
const errorMsg = ref(‘‘);
axios.get(url)
.then((response) => {
loading.value = false;
data.value = response.data;
})
.catch((error) => {
loading.value = false;
errorMsg.value = error.message || ‘未知错误‘;
});
return {
loading,
data,
errorMsg,
};
}
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
应用:当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题:reactive 对象取出的所有属性值都是非响应式的
解决:利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
toRefs的使用
name: {{ name }}
age: {{ age }}
ref 获取元素
利用 ref 函数获取组件中的标签元素
功能需求:让输入框自动获取焦点
ref的另一个作用:可以获取页面中的元素
Composition API(其它部分)
shallowReactive 与 shallowRef
shallowReactive:只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef:只处理了 value 的响应式,不进行对象的 reactive 处理
什么时候用浅响应式呢?
- 一般情况下使用 ref 和 reactive 即可
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化 ===> shallowReactive
- 如果有一个对象数据,后面会产生新的对象来替换 ===> shallowRef
ShallowReactive和ShallowRef
m1: {{ m1 }}
m2: {{ m2 }}
m3: {{ m3 }}
m4: {{ m4 }}
Readonly 与 ShallowReadonly
Readonly:
- 深度只读数据
- 获取一个对象(响应式或纯对象) 或 ref 并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
ShallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
应用场景
- 在某些特定情况下, 我们可能不希望对数据进行更新的操作,那就可以包装生成一个只读代理对象来读取数据,而不能修改或删除
ShallowReadonly和Readonly
state: {{ state }}
toRaw 与 markRaw
toRaw
- 返回由
reactive
或readonly
方法转换成响应式代理的普通对象。 - 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
toRaw和markRaw
state: {{ state }}
toRef
- 为源响应式对象上的某个属性创建一个 ref 对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
- 区别 ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
- 应用: 当要将 某个 prop 的 ref 传递给复合函数时,toRef 很有用
toRef使用及特点
state: {{ state }}
age: {{ age }}
money: {{ money }}
Child子级组件
age: {{ age }}
length: {{ length }}
customRef
- 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
CustomRef的使用
{{ keyword }}
provide 与 inject
- provide和inject提供依赖注入,功能类似 2.x 的provide/inject
- 实现跨层级组件(祖孙)间通信
provide 和 inject
当前的颜色: {{ color }}
儿子组件
孙子组件
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
原文:https://www.cnblogs.com/iamfatotaku/p/15223611.html