前言
在vue项目中,组件是一个极其重要且强大的功能,我们可以通过组件将可以用独立且可复用的小组件来构建大型应用。而组件化,就是将可封装的公共样式、公共逻辑进行封装,大大提高了开发效率,增强了代码的可维护性,以匹配高内聚、低耦合的开发原则。
当我们将页面上一个个组件封装好后,发现有多个组件有着一部分相同的处理逻辑,如果不进行抽离,当需求有所变动时,每一个组件内的逻辑都需要进行调整。因此,我们需要对这些组件的公共逻辑进行抽离。
在vue2中,我们使用mixin进行拆分,但是存在命名冲突,难以维护 ,复用性差等缺点,所以在vue3中,官方取消了mixin方法,取而代之的是组合式函数(Composables),除此之外,官方还提供了自定义指令和插件,
自定义指令:cn.vuejs.org/guide/reusa...
插件:cn.vuejs.org/guide/reusa...
一、 组合式函数
概念与使用场景
在日常的开发中,往往会遇到多个组件内部有公共逻辑,如果将这些逻辑整合为另一组件,那么在该组件中是没有渲染的部分的,称之为无渲染组件
, 无渲染组件内部其实就是封装了有状态逻辑
,然后将动态数据通过插槽的方式,上抛给消费者组件。从而实现了,组件内部仅负责逻辑处理,而布局、样式等处理都交给了消费者组件。这种方式固然可行,但是会带来额外的组件实例的性能开销
。
因此,vue官方就提供了组合式函数,在 Vue 应用的概念中,"组合式函数"(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
❗️当我们
无需考虑视图布局
,只是纯逻辑复用时,推荐用组合式函数替代无渲染组件。
规范约定
组合式函数约定用驼峰命名法命名,并以use
作为开头。组合式函数可接收 ref 参数,所以当我们在编写组合式函数时,最好在内部对入参做兼容 ref 处理。unref()
工具函数会对此非常有帮助,以下是一个简单的示例:
javascript
import { unref } from 'vue'
function useFeature(maybeRef) {
// 若 maybeRef 确实是一个 ref,它的 .value 会被返回
// 否则,maybeRef 会被原样返回
const value = unref(maybeRef)
}
关于返回值,官方推荐组合式函数始终返回一个包含多个 ref 的普通的非响应式对象。目的是为了外部调用组合式函数后,对返回值进行解构为 ref 之后仍可以保持响应性。例如外部调用时使用const { x, y } = useMouse()
使用限制
组合式函数在 <script setup>
或 setup()
钩子中,应始终被同步地 调用。在某些场景下,也可以在像 onMounted()
这样的生命周期钩子中使用他们。这个限制是为了让 Vue 能够确定当前正在被执行的到底是哪个组件实例, 只有能确认当前组件实例,才能够:将组合式函数内的生命周期钩子
注册到该组件实例
上;将组合式函数内的计算属性和监听器
注册到该组件实例
上,以便在该组件被卸载时停止监听,避免内存泄漏。
使用案例
二、自定义指令
注册自定义指令
自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑,例如权限控制等。在 <script setup>
中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令,例如:
xml
<script setup>
// 在模板中启用 v-focus
const vFocus = {
// 类似组件生命周期钩子
// 钩子函数会接收到指令所绑定元素作为其参数
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
当不使用setup时,对应的自定义指令注册方法可见 自定义指令。
自定义指令钩子参数
指令的钩子会传递以下几种参数:
-
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。 -
prevVnode
:代表之前的渲染中指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用。
需要注意的点
当在组件上使用自定义指令时,它会始终应用于组件的根节点,和透传 attributes 类似。但是不同于透传 attributes 在多根节点组件上的表现,自定义指令不能通过 v-bind="$attrs"
来传递给一个明确的根元素。所以不推荐在组件上使用自定义指令。
使用案例
以下是一个在main.ts中通过自定义指令来控制按钮权限的代码,其中checkPermission为引入的检查权限的函数。
scss
Vue.directive("permission", {
inserted (el, binding) {
let permission = binding.value; // 获取到 v-permission的值
if (permission) {
let hasPermission = checkPermission(permission);
if (!hasPermission) { // 没有权限 移除Dom元素
el.parentNode && el.parentNode.removeChild(el);
}
}
}
})
之后就可以在页面中使用该指令做按钮权限控制
ini
<div class="btns">
<button v-permission="'1'">权限按钮1</button>
<button v-permission="'10'">权限按钮2</button>
<button v-permission="'demo'">权限按钮3</button>
</div>
三、插件
概念
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。 一个插件可以是一个拥有 install()
方法的对象,也可以直接是一个安装函数本身。vue实例方法 app.use()
源码内部会对传入的第一个参数进行类型判断。app.use()
还可以接收第二个参数:一个对象形式的可选的选项 options。该参数会被传递给插件内部的安装函数。安装函数会接收到安装它的应用实例和传递给 app.use()
的额外选项 options 作为参数。
❗️请谨慎使用全局属性,如果在整个应用中使用不同插件注入的太多全局属性,很容易让应用变得难以理解和维护。
使用案例
我们希望有一个翻译函数,这个函数接收一个以 .
作为分隔符的 key
字符串,用来在用户提供的翻译字典中查找对应语言的文本。期望的使用方式如下:
bash
<h1>{{ $translate('greetings.hello') }}</h1>
这个函数应当能够在任意模板中被全局调用。这一点可以通过在插件中将它添加到app.config.globalProperties
上来实现:
javascript
export default {
install: (app, options) => { // 注入一个全局可用的 $translate() 方法
app.config.globalProperties.$translate = (key) => { // 获取 options 对象的深层属性
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
}
}
我们的 $translate
函数会接收一个例如 greetings.hello
的字符串,在用户提供的翻译字典中查找,并返回翻译得到的值。
四、使用场景
- 组合式函数 是利用 vue3 组合式 API 来封装和复用有状态逻辑,侧重于纯逻辑 。当纯逻辑无法满足场景,需要加入一些布局样式的实现时,无渲染组件更适合。
- 自定义指令 是为了重用涉及普通元素的底层 DOM 访问的逻辑,一般是在需要通过直接的 DOM 操作才能实现功能的场景下,才会使用的一种技术。
- 插件 侧重于全局的注入,这种注入包括了:全局组件、全局自定义指令、全局资源、全局的实例属性和方法。