在 Vue.js 中使用Mixins

原文链接: Using Mixins in Vue.js
vue文档: mixins文档链接

一个很常见场景:
你有两个非常相似的组件, 它们拥有非常相似的基本功能, 但是它们之间又有足够的不同的地方, 该如何选择呢? 我们是应该将它们分成两个完全不同的组件呢? 还是创建一个基础组件, 然后定义足够多的props以方便区分使用场景?
这两种方式都不是完美的: 如果你将它们分成两个完全不同的组件, 在需求变化(功能变化)时, 可能会增加需要同时修改两个组件的风险, 这违反了"DRY"的前提. 另一方面, 太多的props很快会让人变得凌乱, 并且, 迫使维护人员, 甚至是你自己, 要首先理解这些props的上下文才能使用它, 这会让人非常失望.
VueMixins是非常实用的编程方式, 因为最终实用的编程是通过不断减少运动部件(moving parts)使代码变得容易理解.一个mixin允许你封装一个功能, 以便你能在整个应用程序中的不同组件中使用它.

基础实例

假设我们有一些不同的组件, 它们的工作是切换状态boolean, 一个模态(modal)和一个提示(tooltip). 这些tooltipsmodals没有很多共同之处, 除了这个功能: 它们看起来不一样, 它们使用起来也不尽相同, 但是它们的逻辑是相似的.

//modal
const Modal = {
  template: '#modal',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  },
  components: {
    appChild: Child
  }
}

//tooltip
const Tooltip = {
  template: '#tooltip',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  },
  components: {
    appChild: Child
  }
}

我们可以从中提取逻辑, 并创建可以复用的部分:(在CODEPEN中查看效果)

const toggle = {
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  }
}

const Modal = {
  template: '#modal',
  mixins: [toggle],
  components: {
    appChild: Child
  }
};

const Tooltip = {
  template: '#tooltip',
  mixins: [toggle],
  components: {
    appChild: Child
  }
};

查看 Sarah Drasner(@sdras) 在CodePen上编写 混合的例子

为了更容易理解混合,这个例子故意编写的简单一些。真实应用中使用混合的有,包含但不限于:获取视窗和组件的尺寸,采集特定的鼠标事件和图表的基本元素。

使用

您可以设置您的目录结构以任何您喜欢的方式, 但我喜欢创建一个单独的mixin目录以方便管理它们. 我们将创建的文件将具有".js"扩展名(而不是.vue, 就像我们其他的文件), 然后我们将导出(export)一个对象为mixin:


v2-496d78156ff3ee6174173a6030687f8a_hd.png

然后, 在modal.vue里, 我们可以通过import刚才的toggle来访问它, 就像这样:

import { toggle } from './mixins/toggle'

export default {
  name: 'modal',
  mixins: [toggle],
  components: {
    appChild: Child
  }
}

很重要的需要意识到的一点是, 尽管如此, 我们使用的是一个对象(object)而不是一个组件(component), 生命周期(lifecycle)内的方法(methods)是可用的. 我们可以挂载(hook)到mounted(), 这样它就会被应用于组件的生命周期, 使得这种方法非常的灵活和强大

合并(Merging)

看看最后一个例子, 我们发现, 我们不仅仅拥有我们的功能, 生命周期的钩子(hooks)也可用于mixin, 所以, 当我们将它应用与具有重叠进程(overlapping processes)的时候, 顺序很重要. 默认情况下, mixins将首先被调用, 然后是组件, 所以我们可以根据需要来覆盖它(override). 就是说, 组件有最后的发言权. 这只有当冲突发生时才变得非常重要, 在这个时候, 组件必须决定哪一个胜出, 否则一切将被放置在一个数组中执行, mixins相关的放在前面, 然后是组件相关的.

//mixin
const hi = {
  mounted() {
    console.log('hello from mixin!')
  }
}

//vue instance or component
new Vue({
  el: '#app',
  mixins: [hi],
  mounted() {
    console.log('hello from Vue instance!')
  }
});

//Output in console
> hello from mixin!
> hello from Vue instance!

如果两个冲突, Vue实例和组件将胜出:

//mixin
const hi = {
  methods: {
    sayHello: function() {
      console.log('hello from mixin!')
    }
  },
  mounted() {
    this.sayHello()
  }
}

//vue instance or component
new Vue({
  el: '#app',
  mixins: [hi],
  methods: {
    sayHello: function() {
      console.log('hello from Vue instance!')
    }
  },
  mounted() {
    this.sayHello()
  }
})

// Output in console
> hello from Vue instance!
> hello from Vue instance!

您可能已经注意到, 在Vue实例中, 我们有两个console.log打印而不是一个. 这是因为, mixin解析以后, 在Vue实例中的mounted()里面会有两个this.sayHello()调用, 但是由于mixin中的sayHello函数会在Vue实例的sayHello函数之前, 因此被Vue实例中的sayHello函数覆盖了. 所以, mounted()中调用的两次sayHello实际上都是Vue实例中的sayHello函数.

全局Mixins

当我们使用术语global参考mixins时, 我们不是指能够在每个组件访问它们, 就像过滤器一样. 我们已经可以在组件中访问mixins以这种方式: mixins: [toggle].

Global mixins如字面上的意思一样, 会被应用于所有的组件. 因此, 它们的用例非常有限, 应该慎重考虑. 我可以想到的能够合理使用的一个场景是像插件一样的东西, 你可能需要访问一切. 但是, 即使在这种情况下, 我也会对您正在应用的内容感到担心, 特别是当您将功能扩展到可能是黑匣子的应用程序时.

要创建全局实例, 我们应将其放在Vue实例之前. 在典型的Vue-cli构建中, 这将在您的main.js文件中.

Vue.mixin({
  mounted() {
    console.log('hello from mixin!')
  }
})

new Vue({
  ...
})

还是那句话, 谨慎使用这种方式的mixin! 现在, mixin中的console.log将会出现在每一个单独的组件中. 在这种情况下还不是很糟(除了控制台里面多了许多噪音外). 但是您会发现, 如果使用不当, 将会带来多大的危害.

结论

混合对于封装一小段想要复用的代码来讲是有用的。对你来说它们当然不是唯一可行的选择:高阶组件,例如,允许组合相似函数,这只是实现的一种方式。我喜欢混合,因为我不需要传递状态,但是这种模式当然也可能会被滥用,所以,仔细思考哪种选择对你的应用最有意义。

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

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,051评论 0 29
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,216评论 0 6
  • Vue是一个前端js框架,由尤雨溪开发,是个人项目 Vue近几年来特别的受关注,三年前的时候angularJS霸占...
    6e5e50574d74阅读 554评论 0 0
  • 深入响应式 追踪变化: 把普通js对象传给Vue实例的data选项,Vue将使用Object.defineProp...
    冥冥2017阅读 4,874评论 6 16
  • 漂泊//叁善万物 没领略过名山大川 没领略过古迹新创举 生活在弹丸之域 涉足远方是我梦里的梦幻 没漂泊过大小湖泊 ...
    叁善萬物阅读 1,265评论 9 75