composition API 和 Mixins
关于Vue3.0的Composition API 和 旧版本的Mixins【转载】
原文:http://caibaojian.com/vue3-composition-api.html
在过往,如果你想共享组件间的代码,一般使用的方式是利用 mixin 来实现。但是在Vue3.0 中提供了更好的解决方案 Composition API。
下面我们将介绍 Mixins 的缺点,并且看看 Composition API 是如何克服这些缺点的。
Mixins简述
我们先来回顾以下Mixins的模式。以下的内容是十分重要的需要认真阅读。
通常情况下,一个Vue组件是由一个Javascript对象来定义的,这个Javascript对象具有各种属性,代表着我们需要的功能 --- 比如data
,method
,computed
等。
// MyComponent.js
export default {
data:()=>({
myDataProperty: null
}),
methods:{
myMethod () { ... }
},
// ....
}
当我们想在组件之间共享相同的属性的时候,就可以将共同的属性提取出来,存放到一个单独的模块中。
// MyMixins.js
export default {
data:()=>({
mySharedDataProperty: null
}),
methods:{
blabla(){...}
}
}
现在我们通过将其分配给 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 缺点
1. 命名冲突
我们看到minxin模式是如何在运行时合并两个对象的。如果它们都共享一个同名的属性会发生什么
const mixin = {
data: ()=>({
myProp:null
})
}
export default {
mixins:[mixin],
data:()=>({
// same name !
myProp:null
})
}
这就是合并策略发挥的地方。这一组规则用于决定一个组件包含多个相同名称的选项时的情况。
Vue组件的默认(但可选择配置)合并策略决定了本地选项将覆盖混合器选项。但也有例外,例如我们有多个相同类型的生命周期钩子,那么这些钩子将被添加到钩子数组中,并且所有的钩子将被依次调用。
尽管我们不应该遇到任何实际的错误,但当我们在多个组件和混合体之间杂耍命名的属性时,写代码会越来越困难。尤其当第三方的混合组件被添加为npm包时,就更难了,因为它们的命名属性可能会引起冲突。
2.隐含的依赖关系
混合器和消耗它的组件之间没有层次关系。这意味着,组件可以使用混入器中定义的数据属性(如mySharedDataProperty),但混入器也可以使用它假定在组件中定义的数据属性(如myLocalDataProperty)。当混合器被用于共享输入验证时,通常会出现这种情况。mixin可能会期望一个组件有一个输入值,它将在自己的validate方法中使用。
但这可能会导致问题。如果我们以后想重构一个组件并改变了mixin需要的变量的名称,我们会发现在运行时会出现报错。
现在想象一下一个有一大堆 mixin 的组件,我们可以重构本地数据吗?我们可以重构一个本地数据属性吗?或者会不会破坏一个混搭?哪一个混杂项呢?我们必须手动搜索它们才能知道。
这些缺点的存在,就是Composition API背后的主要动因之一,下面先粗略的介绍了Composition API的工作原理
关于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
}
}
}
tips
- 反应式变量: 在一个 Y = f(x) 的函数中 Y随着X的变化而变化,那么 Y 就是反应式变量(又叫因变量)
首先你会注意到我们导入了一个 ref 函数,这使得我们可以定义一个反应式变量,其功能与数据变量基本相同。计算函数也一样
增量方法(increment)不是反应式的,所以它可以被声明为一个普通的Javascript函数。注意,我们需要改变子属性值,才能改变 count 反应式变量的值。这是因为使用 ref 创建的反应式变量在传递过程中,需要将其作为对象来保留反应式变量
关于 ref 的工作原理的详细解释,请参考 Vue Composition API 文档,这是个好主意。
一旦我们定义了这些功能,我们就从setup函数中返回这些功能。上面的两个组件在功能上没有什么区别。我们所做的就是使用替代API。
代码提取
Composition API 的第一个明显的优势是很容易提取逻辑
让我们用Composition API重构上面定义的组件,这样我们定义的特征就在一个Javascript模块useCounter中。(用"use"作为特征描述的前缀是Composition 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 是更加优越的。