译文:Vue3 Composition API 是如何取代 Vue Mixins 的?

原文:https://css-tricks.com/how-the-Vue-composition-api-replaces-vue-mixins/

译文:http://caibaojian.com/vue3-composition-api.html

Vue 3已经更新到beta.2 了,你对它了解多少,如果不知道可以看看我的前一篇文章介绍,今天在CSSTricks上看到一篇关于Composition API介绍,来看看它是如何克服Mixins的缺点的。译文如有纰漏,还请见谅,以下为正文。

------------------

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

在这篇文章中,我们将看看Mixins的缺点,并看看Composition API是如何克服这些缺点,让Vue应用的可扩展性更强。

Mixins简述

让我们快速回顾一下mixins模式,因为对于我们在接下来的章节中所涉及到的内容来说,让我们把它放在首位是很重要的。

通常情况下,一个Vue组件是由一个JavaScript对象来定义的,这个JavaScript对象具有各种属性,代表着我们需要的功能--比如datamethodscomputed​等。

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

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

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

现在,我们可以通过将其分配给mixin config属性并将其添加到任何使用的组件中。在运行时,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 () { ... }
  }
}

Mixins被认为是有害的

早在2016年年年中,Dan Abramov写了《Mixins被认为是有害的》一文,他在文中认为,在React组件中使用mixins来重用逻辑是一种反模式,主张远离mixins。

不幸的是,他提到的关于React mixins的缺点也同样适用于Vue。让我们先熟悉一下这些缺点,然后再来看看Composition API是如何克服这些缺点的。

命名冲突

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

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

这就是合并策略发挥作用的地方。这是一组规则,用于决定当一个组件包含多个相同名称的选项时的情况。

Vue 组件的默认(但可选择配置)合并策略决定了本地选项将覆盖混合器选项。但也有例外。例如,如果我们有多个相同类型的生命周期钩子,那么这些钩子将被添加到钩子数组中,并且所有的钩子将被依次调用。

尽管我们不应该遇到任何实际的错误,但当我们在多个组件和混合体之间杂耍命名的属性时,写代码会变得越来越困难。尤其是当第三方的混合组件被添加为npm包时,这就更难了,因为它们的命名属性可能会引起冲突。

隐含的依赖关系

混合器和消耗它的组件之间没有层次关系。这意味着,组件可以使用混入器中定义的数据属性(如mySharedDataProperty),但混入器也可以使用它假定在组件中定义的数据属性(如myLocalDataProperty)。当混合器被用于共享输入验证时,通常会出现这种情况。mixin可能会期望一个组件有一个输入值,它将在自己的validate方法中使用。

但这可能会导致问题。如果我们以后想重构一个组件并改变了mixin需要的变量的名称,会发生什么情况呢?我们在看这个组件时,不会发现有什么问题。linter也不会发现它。我们只会在运行时看到错误。

现在想象一下一个有一大堆mixin的组件,我们可以重构一个本地数据吗?我们可以重构一个本地数据属性吗,或者会不会破坏一个混搭?哪一个混杂项呢?我们必须手动搜索它们才能知道。

从mixin迁移过来

Dan的文章提供了一些替代mixins的方法,包括高阶组件、实用方法和其他一些组件组成模式。

虽然Vue在很多方面与React相似,但他建议的替代模式并不能很好地转化为Vue。所以,尽管这篇文章写于2016年年年中,但从那时起,Vue开发者们就一直在忍受着mixin问题的困扰。

直到现在。mixins 的缺点是 Composition API 背后的主要动因之一。在看看它是如何克服mixins的问题之前,让我们先快速了解一下它的工作原理。

Composition API速成课程

组成API的关键思想是,我们将组件的功能(如状态、方法、计算属性等)定义为对象属性,而不是将其定义为从新的设置函数中返回的JavaScript变量。

以这个经典的Vue 2组件为例,它定义了一个 "计数器 "功能。

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

下面是使用 Composition API 定义的完全相同的组件。

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

首先你会注意到我们导入了一个ref函数,这使得我们可以定义一个反应式变量,其功能与数据变量基本相同。计算函数也是一样的。

增量方法不是反应式的,所以它可以被声明为一个普通的JavaScript函数。注意,我们需要改变子属性值,才能改变count反应式变量的值。这是因为使用 ref 创建的反应式变量在传递过程中,需要将其作为对象来保留反应式变量。

关于 ref 的工作原理的详细解释,请参考 Vue Composition API 文档,这是个好主意。

一旦我们定义了这些功能,我们就从setup函数中返回这些功能。上面的两个组件在功能上没有什么区别。我们所做的就是使用替代API。

提示:Composition API将是Vue 3的核心功能,但你也可以在Vue 2中通过NPM插件@vue/composition-api使用它。

代码提取

Composition API的第一个明显优势是很容易提取逻辑。

让我们用Component API重构上面定义的组件,这样我们定义的特征就在一个JavaScript模块useCounter中。(用 "use "作为特征描述的前缀是Component API的命名惯例。)

//useCounter.js
import { ref, computed } from "vue";export default function () {
  const count = ref(0);
  const double = computed(() => count * 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
    }
  }
}

这一切可能一开始看起来有点啰嗦,也没有意义,但是让我们来看看这个模式如何克服我们前面看的mixins的问题。

命名冲突....解决了!

我们之前已经看到了一个混搭元素如何使用可能与消耗组件中的属性名称相同的属性,甚至更阴险的是,在消耗组件使用的其他混搭元素中也会有相同的名称。

这并不是 Composition API 的问题,因为我们需要显式命名任何状态或从组成函数返回的方法。

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

命名碰撞的解决方法将与其他任何JavaScript变量的命名方式一样。

隐式依赖关系.....解决了!

我们之前也看到了一个组合函数可能会使用消耗组件上定义的数据属性,这可能会使代码变得很脆弱,而且很难推理。

而组合函数也可以调用消耗组件中定义的本地变量。但不同的是,这个变量现在必须显式传递给组成函数。

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无疑是优越的。

------------------

译文完

附加评论:

喜欢这个解释和代码比较。 这是我真正理解的对Composition API的第一个解释(诚然,我并没有对此进行深入研究)。 我没有意识到它的目标是解决mixin模式的问题,但是您在此处概述的方式很有道理。 谢谢!
原文:译文:Vue3 Composition API 是如何取代 Vue Mixins 的? ,未经许可,禁止转载。
来源:前端开发博客 (http://caibaojian.com/vue3-composition-api.html)