一、组合式函数
一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
1、命名
组合式函数约定用驼峰命名法命名,并以“use”作为开头。
2、输入参数
尽管其响应性不依赖 ref,组合式函数仍可接收 ref 参数。如果编写的组合式函数会被其他开发者使用,你最好在处理输入参数时兼容 ref 而不只是原始的值。unref() 工具函数会对此非常有帮助:
import { unref } from 'vue'
function useFeature(maybeRef) {
// 若 maybeRef 确实是一个 ref,它的 .value 会被返回
// 否则,maybeRef 会被原样返回
const value = unref(maybeRef)
}
如果你的组合式函数在接收 ref 为参数时会产生响应式 effect,请确保使用 watch() 显式地监听此 ref,或者在 watchEffect() 中调用 unref() 来进行正确的追踪。
3、返回值
你可能已经注意到了,我们一直在组合式函数中使用 ref() 而不是 reactive()。
推荐的约定是组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以保持响应性:
如果以对象属性的形式来使用组合式函数中返回的状态,你可以将返回的对象用 reactive() 包装一次,这样其中的 ref 会被自动解包,例如:
const mouse = reactive(useMouse())
// mouse.x 链接到了原来的 x ref
console.log(mouse.x)
template
Mouse position is at: {{ mouse.x }}, {{ mouse.y }}
二、自定义指令
1、基本用法
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令:
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
// 当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦
<input v-focus />
</template>
在没有使用 <script setup> 的情况下,自定义指令需要通过 directives 选项注册:
js
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}
自定义指令全局注册到应用层级:
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
TIP:
只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。其他情况下应该尽可能地使用 v-bind 这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好。
2、指令钩子
一个指令的定义对象可以提供几种钩子函数 (都是可选的):
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) {}
}
3、钩子参数
指令的钩子会传递以下几种参数:(除了 el 外,其他参数都是只读的,不要更改它们)
● 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 钩子中可用。
举例:
template<div v-example:foo.bar="baz">
// binding 参数会是一个这样的对象:
js{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
和内置指令类似,自定义指令的参数也可以是动态的。举例来说:
// 这里指令的参数会基于组件的 arg 数据属性响应式地更新。
<div v-example:[arg]="value"></div>
4、简化形式
template<div v-color="color"></div>
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
5、在组件上使用
当在组件上使用自定义指令时,它会始终应用于组件的根节点,和透传 attributes 类似。
<MyComponent v-demo="test" />
template<!-- MyComponent 的模板 -->
<div> <!-- v-demo 指令会被应用在此处 -->
<span>My component content</span>
</div>
当应用到一个多根组件时,指令将会被忽略且抛出一个警告。和 attribute 不同,指令不能通过 v-bind="$attrs" 来传递给一个不同的元素。
总的来说,不推荐在组件上使用自定义指令。
三、插件
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。
1、基本用法
一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装它的应用实例和传递给 app.use() 的额外选项作为参数
安装插件示例:
import { createApp } from 'vue'
const app = createApp({})
const myPlugin = {
install(app, options) {
// 配置此应用
}
}
app.use(myPlugin. {})
2、编写一个插件
i18n (国际化 (Internationalization) 的缩写)
要求:
1、 $translate 函数会接收一个例如 greetings.hello 的字符串,在用户提供的翻译字典中查找,并返回翻译得到的值
2、 $translate('greetings.hello') 在运行时被替换为 Bonjour!
3、 这个函数应当能够在任意模板中被全局调用
template
<h1>{{ $translate('greetings.hello') }}</h1>
js
// plugins/i18n.js
export default {
install: (app, options) => {
// 注入一个全局可用的 $translate() 方法
app.config.globalProperties.$translate = (key) => {
// 获取 `options` 对象的深层属性
// 使用 `key` 作为索引
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
}
}
js
import i18nPlugin from './plugins/i18n'
// 用于查找的翻译字典对象则应当在插件被安装时作为 app.use() 的额外参数被传入
app.use(i18nPlugin, {
greetings: {
hello: 'Bonjour!'
}
})
方法二:结合provide和inject
js
// plugins/i18n.js
export default {
install: (app, options) => {
app.config.globalProperties.$translate = (key) => {
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
app.provide('i18n', options)
}
}
vue
<script setup>
import { inject } from 'vue'
const i18n = inject('i18n')
console.log(i18n.greetings.hello)
</script>