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 是更加优越的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342