本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
组合式函数
定义: 利用vue的组合式api来封装有状态的函数,来达到复用的目的
无状态函数:如格式化日期函数,格式化手机号,以达到保护用户隐私的目的的中间是**** 等都是无状态的函数,无状态函数又称纯函数 纯函数的输入输出有一对一的映射关系,无论它被调用多少次,也同样大概率是无副作用函数
有状态函数: 有状态逻辑负责管理,会随时间或外部变量而变化的状态。
// 这就是一个纯函数,也无副作用,因为它不会改变这两个参数,同时,无论任何时候,他的值都用ab决定
function add(a, b) {
return a + b
}
// 有状态的函数,有副作用的函数
// 这里的函数,它每次在执行的时候都会使得temp的值发生变化,这就是副作用函数,
// 同时,他也是有状态的,他的状态取决于他的temp
var temp = 3
function add2(a, b) {
temp = temp + 1;
return a + b + temp
}
如上面的写法一样,大多数情况下,我们肯定提倡的是无状态函数,这样幂等性是得到保障的,函数也无副作用,降低了bug的触发,但是挺多时候,由于业务的复杂,代码的确需要这样的有状态的函数,所以vue3的写法也是在一定程度上是想规范我们对于有状态函数的写法,尽量进行规范,文档中的例子并不是主要的,主要的是他的约定和最佳实践反而更有借鉴意义
首先是命名:
组合式函数约定用驼峰命名法命名,并以“use”作为开头。
输入参数
尽管其响应性不依赖 ref,组合式函数仍可接收 ref 参数。如果编写的组合式函数会被其他开发者使用,你最好在处理输入参数时兼容 ref 而不只是原始的值。unref() 工具函数会对此非常有帮助:
import { unref } from 'vue'
function useFeature(maybeRef) {
// 若 maybeRef 确实是一个 ref,它的 .value 会被返回
// 否则,maybeRef 会被原样返回
const value = unref(maybeRef)
}
刚好这里了解下新的API: unref()
这是一个语法糖,如果参数是ref,则返回内部值,否则返回参数本身,看着比较懵,代码奉上,就是一个isRef()语法糖
val = isRef(val) ? val.value : val
返回值
继续来看组合式函数的返回值,我们在组合式函数中一直使用ref而不是reactive,这是因为返回是一个包含了多个ref的普通非响应式对象,这样是为了后续函数在组件中被调用时,在被解构后,仍然保持响应式
// x 和 y 是两个 ref
const { x, y } = useMouse()
副作用
虽然我们前面说了不推进写带有副作用的函数,但是很多时候这又是必须的,比如我们要添加dom的事件监听器,或者在调用时进行网络请求,这个时候需要针对调用和生命周期钩子函数进行注意:
- 如果使用了SSR(服务端渲染),那必须等到组件挂载后再调用生命周期函数进行dom相关操作,
- 在调用结束后,必须在onUnmounted()中清理副作用,如事件监听,这个时候应该要移除,
更看重的是和vue2的mixin模式的比较
- mixin不清晰的数据来源,就是当我们使用了多个mixin的时候,我们不知道一个数据具体来自哪个mixin,组合式函数则通过ref和解构模式来让属性和来源一目了然
- 命名空间冲突,多个mixin作者如果使用相同的属性名,则会造成命名冲突,但是通过组合式函数,则可以在解构的时候,重新命名,避免冲突,同时给与使用者更大的自由度
正是基于上面这些缺点,在vue3中,文档中也明确说明了不推荐继续使用mixin,保留该API更多的考量是为了项目迁移的需求
文档中最后提到的和React的hook的对比我们就不记录了,免得被说是碰瓷哈哈哈。感兴趣的自行了解,react是非常优秀的框架,不影响我们去学习和了解他,毕竟vue本身也借鉴了很多react的东西
自定义指令
vue允许你自定义一些类似v-modal/ v-show 的指令,自定义指令主要是为了重用涉及普通元素的底层DOM访问的逻辑
下面先看一个例子,vue3的自定义指令和vue2还是不同的,这里详细记录下
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
这是一个input被vue插入到dom后,自动聚焦的指令,接下来我们了解规则 在** < script setup > ** 中,任何以v开头的驼峰式命名的变量都可以被用作一个自定义指令,如上面的例子中,vFocus使用时就是v-focus
而如果没有在** < script setup > ** 中,自定义指令则需要通过directive选项注册
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
指令钩子
这是我们进行自定义指令编写时最重要的工具:
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
这里的参数的定义是:
钩子参数
指令的钩子会传递以下几种参数:
-
el:指令绑定到的元素。这可以用于直接操作 DOM。 -
binding:一个对象,包含以下属性。value:传递给指令的值。例如在v-my-directive="1 + 1"中,值是2。oldValue:之前的值,仅在beforeUpdate和updated中可用。无论值是否更改,它都可用。arg:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo中,参数是"foo"。modifiers:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar中,修饰符对象是{ foo: true, bar: true }。instance:使用该指令的组件实例。dir:指令的定义对象。
-
vnode:代表绑定元素的底层 VNode。 -
prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在beforeUpdate和updated钩子中可用。
文档中的例子:
<div v-example:foo.bar="baz">
// binding参数会是一个这样的对象
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
插件
文档第一部分说了插件的使用方式,和vue2基本相同,app.use(myplugin,{}) 文档第二部分是我之前在vue2文档这个位置没有看到过的,编写一个插件,之前更多的做为一个小菜鸡,是找别人的插件来使用,比如lodash这类工具函数,插件常见的功能用途是
- 注册一个到多个全局组件或自定义指令
- 提供一个资源provide,使其可被整个应用使用,
- 添加一些全局实例属性活方法
编写一个插件这里的例子大家自己去文档中看吧,是个很好的例子,但是个人还是更想把一些自定义指令整合后,写成一个插件。