Vue 组成 API 如何取代 Vue Mixin

Avatar of Anthony Gore
Anthony Gore

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 $200 免费额度!

想要在 Vue 组件之间共享代码吗?如果您熟悉 Vue 2,您可能已经为这个目的使用过一个 mixin。但是新的 组成 API,现在可作为 Vue 2 的插件使用,也是 Vue 3 的一项即将推出的功能,提供了更好的解决方案。

在本文中,我们将看看 mixin 的缺点,并了解组成 API 如何克服这些缺点,并使 Vue 应用程序更具可扩展性。

简而言之的 Mixin

让我们快速回顾一下 mixin 模式,因为它对于我们在下一节中涵盖的内容至关重要。

通常,Vue 组件由一个 JavaScript 对象定义,该对象具有代表我们所需功能的各种属性——例如 datamethodscomputed 等。

// MyComponent.js
export default {
  data: () => ({
    myDataProperty: null
  }),
  methods: {
    myMethod () { ... }
  }
  // ...
}

当我们想要在组件之间共享相同的属性时,可以将公共属性提取到一个单独的模块中

// MyMixin.js
export default {
  data: () => ({
    mySharedDataProperty: null
  }),
  methods: {
    mySharedMethod () { ... }
  }
}

现在,我们可以通过将其分配给 mixin 配置属性将此 mixin 添加到任何使用它的组件。在运行时,Vue 将组件的属性与任何添加的 mixin 的属性合并。

// ConsumingComponent.js
import MyMixin from "./MyMixin.js";


export default {
  mixins: [MyMixin],
  data: () => ({
    myLocalDataProperty: null
  }),
  methods: {
    myLocalMethod () { ... }
  }
}

对于这个具体的例子,运行时使用的组件定义将如下所示

export default {
  data: () => ({
    mySharedDataProperty: null
    myLocalDataProperty: null
  }),
  methods: {
    mySharedMethod () { ... },
    myLocalMethod () { ... }
  }
}

Mixin 被认为是“有害的”

早在 2016 年中旬,Dan Abramov 就写了 “Mixin 被认为是有害的”,他在文章中认为,在 React 组件中使用 mixin 来重用逻辑是一种反模式,主张远离它们。

他提到的关于 React mixin 的相同缺点,不幸的是,也适用于 Vue。在我们看看组成 API 如何克服这些缺点之前,让我们先熟悉一下这些缺点。

命名冲突

我们已经看到了 mixin 模式如何在运行时合并两个对象。如果它们都共享一个同名的属性会发生什么?

const mixin = {
  data: () => ({
    myProp: null
  })
}


export default {
  mixins: [mixin],
  data: () => ({
    // same name!
    myProp: null
  })
}

这就是 合并策略 的作用。这是用于确定当组件包含多个具有相同名称的选项时会发生什么的规则集。

Vue 组件的默认(但可配置)合并策略规定本地选项将覆盖 mixin 选项。但也有一些例外。例如,如果我们有多个相同类型的生命周期钩子,它们将被添加到钩子数组中,并且所有钩子都将按顺序调用。

即使我们不会遇到任何实际的错误,在多个组件和 mixin 之间处理命名属性时,编写代码变得越来越困难。尤其是在添加第三方 mixin 作为 npm 包时,它们自己的命名属性可能会导致冲突,这将使编写代码变得更加困难。

隐式依赖关系

mixin 和使用它的组件之间没有层次关系。这意味着组件可以使用 mixin 中定义的数据属性(例如 mySharedDataProperty),但 mixin 也可以使用它假设在组件中定义的数据属性(例如 myLocalDataProperty)。当使用 mixin 来共享输入验证时,这种情况很常见。mixin 可能会期望组件具有它将在自己的验证方法中使用的输入值。

不过,这可能会导致问题。如果我们以后想要重构组件并更改 mixin 需要的一个变量的名称,会发生什么?我们不会从组件中看到有什么问题。代码校验器也不会发现问题。我们只会在运行时看到错误。

现在想象一个带有许多 mixin 的组件。我们可以重构一个本地数据属性,还是会破坏 mixin?哪个 mixin?我们需要手动搜索所有 mixin 才能知道。

从 mixin 迁移

Dan 的文章提供了 mixin 的替代方案,包括高阶组件、实用方法以及其他一些组件组合模式。

虽然 Vue 在很多方面与 React 类似,但他建议的替代模式并不适用于 Vue。因此,尽管这篇文章是在 2016 年年中写成的,但 Vue 开发人员一直都在忍受 mixin 的问题。

直到现在。mixin 的缺点是组成 API 的主要推动因素之一。让我们快速了解一下它的工作原理,然后再看看它如何克服 mixin 的问题。

组成 API 速成课程

组成 API 的核心思想是,与其将组件的功能(例如状态、方法、计算属性等)定义为对象属性,不如将它们定义为从新的 setup 函数返回的 JavaScript 变量。

以下是一个定义了“计数器”功能的 Vue 2 组件的经典示例

//Counter.vue
export default {
  data: () => ({
    count: 0
  }),
  methods: {
    increment() {
      this.count++;
    }
  },
  computed: {
    double () {
      return this.count * 2;
    }
  }
}

以下是使用组成 API 定义的完全相同的组件。

// Counter.vue
import { ref, computed } from "vue";


export default {
  setup() {
    const count = ref(0);
    const double = computed(() => count.value * 2)
    function increment() {
      count.value++;
    }
    return {
      count,
      double,
      increment
    }
  }
}

首先你会注意到我们导入了一个 ref 函数,它允许我们定义一个响应式变量,它的功能与 data 变量几乎相同。computed 函数也是如此。

increment 方法不是响应式的,因此可以声明为一个普通的 JavaScript 函数。请注意,我们需要更改子属性 value 才能更改 count 响应式变量的值。这是因为使用 ref 创建的响应式变量需要是对象才能在传递时保持它们的响应性。

建议您查阅 Vue 组成 API 文档,以详细了解 ref 的工作原理。

定义好这些功能后,我们将其从 setup 函数中返回。以上两个组件在功能上没有任何区别。我们所做的只是使用了另一种 API。

提示:组成 API 将成为 Vue 3 的核心功能,但您也可以在 Vue 2 中使用 NPM 插件 @vue/composition-api

代码提取

组成 API 的第一个明显优势是它易于提取逻辑。

让我们使用组成 API 重构上面定义的组件,以便我们在一个 JavaScript 模块 useCounter 中定义功能。(在功能描述之前加上“use”是组成 API 的命名约定。)

// useCounter.js
import { ref, computed } from "vue";


export default function () {
  const count = ref(0);
  const double = computed(() => count.value * 2)
  function increment() {
    count.value++;
  }
  return {
    count,
    double,
    increment
  }
}

代码重用

要在组件中使用该功能,我们只需将模块导入组件文件并调用它(注意,导入的是一个函数)。这将返回我们定义的变量,然后我们可以从 setup 函数中返回这些变量。

// MyComponent.js
import useCounter from "./useCounter.js";

export default {
  setup() {
    const { count, double, increment } = useCounter();
    return {
      count,
      double,
      increment
    }
  }
}

一开始这可能看起来有点冗长,毫无意义,但让我们看看这种模式如何克服我们之前提到的 mixin 的问题。

命名冲突……解决了!

我们之前看到,mixin 可以使用可能与使用它的组件中的属性同名,甚至更糟糕的是,与使用它的组件使用的其他 mixin 中的属性同名的属性。

组成 API 不会出现这个问题,因为我们需要明确地命名从组合函数返回的任何状态或方法

export default {
  setup () {
    const { someVar1, someMethod1 } = useCompFunction1();
    const { someVar2, someMethod2 } = useCompFunction2();
    return {
      someVar1,
      someMethod1,
      someVar2,
      someMethod2
    }
  }
}

命名冲突将以与任何其他 JavaScript 变量相同的方式解决。

隐式依赖... 已解决!

我们之前也看到过 mixin 如何使用消费组件定义的数据属性,这会导致代码变得脆弱且难以理解。

组合函数也可以调用消费组件中定义的局部变量。不过,区别在于现在必须将此变量显式传递给组合函数。

import useCompFunction from "./useCompFunction";


export default {
  setup () {
    // some local value the a composition function needs to use
    const myLocalVal = ref(0);


    // it must be explicitly passed as an argument
    const { ... } = useCompFunction(myLocalVal);
  }
}

总结

mixin 模式表面上看起来很安全。但是,通过合并对象来共享代码成为了反模式,因为它会增加代码的脆弱性,并掩盖了理解功能的能力。

Composition API 最巧妙的地方在于它允许 Vue 依靠内置于原生 JavaScript 中的安全措施来共享代码,例如将变量传递给函数以及模块系统。

这意味着 Composition API 在各方面都优于 Vue 的经典 API 吗?不,在大多数情况下,你可以继续使用经典 API。但如果你打算重用代码,Composition API 无疑更胜一筹。