在vue3项目中,如何复用多个组件的公共逻辑?

发布于:2024-05-08 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言

在vue项目中,组件是一个极其重要且强大的功能,我们可以通过组件将可以用独立且可复用的小组件来构建大型应用。而组件化,就是将可封装的公共样式、公共逻辑进行封装,大大提高了开发效率,增强了代码的可维护性,以匹配高内聚、低耦合的开发原则。

当我们将页面上一个个组件封装好后,发现有多个组件有着一部分相同的处理逻辑,如果不进行抽离,当需求有所变动时,每一个组件内的逻辑都需要进行调整。因此,我们需要对这些组件的公共逻辑进行抽离。

在vue2中,我们使用mixin进行拆分,但是存在命名冲突,难以维护 ,复用性差等缺点,所以在vue3中,官方取消了mixin方法,取而代之的是组合式函数(Composables),除此之外,官方还提供了自定义指令和插件,

自定义指令:

插件:

一、 组合式函数

  1. 概念与使用场景

在日常的开发中,往往会遇到多个组件内部有公共逻辑,如果将这些逻辑整合为另一组件,那么在该组件中是没有渲染的部分的,称之为无渲染组件 无渲染组件内部其实就是封装了有状态逻辑,然后将动态数据通过插槽的方式,上抛给消费者组件。从而实现了,组件内部仅负责逻辑处理,而布局、样式等处理都交给了消费者组件。这种方式固然可行,但是会带来额外的组件实例的性能开销

因此,vue官方就提供了组合式函数,在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。

❗️当我们无需考虑视图布局,只是纯逻辑复用时,推荐用组合式函数替代无渲染组件。

  1. 规范约定

组合式函数约定用驼峰命名法命名,并以use作为开头。组合式函数可接收 ref 参数,所以当我们在编写组合式函数时,最好在内部对入参做兼容 ref 处理。unref() 工具函数会对此非常有帮助,以下是一个简单的示例:

import { unref } from 'vue'

function useFeature(maybeRef) {
  // 若 maybeRef 确实是一个 ref,它的 .value 会被返回
  // 否则,maybeRef 会被原样返回
  const value = unref(maybeRef)
}

关于返回值,官方推荐组合式函数始终返回一个包含多个 ref 的普通的非响应式对象。目的是为了外部调用组合式函数后,对返回值进行解构为 ref 之后仍可以保持响应性。例如外部调用时使用const { x, y } = useMouse()

  1. 使用限制

组合式函数在 <script setup>setup() 钩子中,应始终被同步地调用。在某些场景下,也可以在像 onMounted() 这样的生命周期钩子中使用他们。这个限制是为了让 Vue 能够确定当前正在被执行的到底是哪个组件实例, 只有能确认当前组件实例,才能够:将组合式函数内的生命周期钩子注册到该组件实例上;将组合式函数内的计算属性和监听器注册到该组件实例上,以便在该组件被卸载时停止监听,避免内存泄漏。

  1. 使用案例

二、自定义指令

  1. 注册自定义指令

自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑,例如权限控制等。在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令,例如:

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  // 类似组件生命周期钩子
  // 钩子函数会接收到指令所绑定元素作为其参数
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>

当不使用setup时,对应的自定义指令注册方法可见 。

  1. 自定义指令钩子参数

指令的钩子会传递以下几种参数:

  • el:指令绑定到的元素。这可以用于直接操作 DOM。

  • binding:一个对象,包含以下属性。

    • value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
    • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
  • vnode:代表绑定元素的底层 VNode。

  • prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdateupdated 钩子中可用。

  1. 需要注意的点

当在组件上使用自定义指令时,它会始终应用于组件的根节点,和 类似。但是不同于透传 attributes 在多根节点组件上的表现,自定义指令不能通过 v-bind="$attrs" 来传递给一个明确的根元素。所以不推荐在组件上使用自定义指令。

  1. 使用案例

以下是一个在main.ts中通过自定义指令来控制按钮权限的代码,其中checkPermission为引入的检查权限的函数。

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);
      }
    }
  }
})

之后就可以在页面中使用该指令做按钮权限控制

<div class="btns">
  <button v-permission="'1'">权限按钮1</button>
  <button v-permission="'10'">权限按钮2</button>
  <button v-permission="'demo'">权限按钮3</button>
</div>

三、插件

  1. 概念

插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。 一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。vue实例方法 app.use() 源码内部会对传入的第一个参数进行类型判断。app.use() 还可以接收第二个参数:一个对象形式的可选的选项 options。该参数会被传递给插件内部的安装函数。安装函数会接收到安装它的和传递给 app.use() 的额外选项 options 作为参数。

❗️请谨慎使用全局属性,如果在整个应用中使用不同插件注入的太多全局属性,很容易让应用变得难以理解和维护。

  1. 使用案例

我们希望有一个翻译函数,这个函数接收一个以 . 作为分隔符的 key 字符串,用来在用户提供的翻译字典中查找对应语言的文本。期望的使用方式如下:

<h1>{{ $translate('greetings.hello') }}</h1>

这个函数应当能够在任意模板中被全局调用。这一点可以通过在插件中将它添加到app.config.globalProperties 上来实现:

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 操作才能实现功能的场景下,才会使用的一种技术。
  • 插件侧重于全局的注入,这种注入包括了:全局组件、全局自定义指令、全局资源、全局的实例属性和方法。