vue使用mixins优化组件

vue 提供了 mixins 这个 API,可以让我们将组件中的可复用功能抽取出来,放入 mixin 中,然后在组件中引入 mixin,可以让组件显得不再臃肿,提高了代码的可复用性。

如何理解 mixins 呢 ?我们可以将 mixins 理解成一个数组,数组中有单或多个 mixin,mixin 的本质就是一个 JS 对象,它可以有 data、created、methods 等等 vue 实例中拥有的所有属性,甚至可以在 mixins 中再次嵌套 mixins,It's amazing !

举个简单的栗子:

<div id="app">
  <h1>{{ message }}</h1>
</div>
const myMixin = {
  data() {
    return {
      message: 'this is mixin message'
    }
  },
  created() {
    console.log('mixin created')
  }
}

const vm = new Vue({
  el: '#app',
  mixins: [myMixin],

  data() {
    return {
      message: 'this is vue instance message'
    }
  },
  created() {
    console.log(this.message)
    // => Root Vue Instance
    console.log('vue instance created')
    // => created myMixin
    // => created Root Vue Instance
  }
})

mixins 与 Vue Instance 合并时,会将 created 等钩子函数合并成数组,mixins 的钩子优先调用,当 data、methods 对象键值冲突时,以组件优先。

PS: 如果对 mixins 的概念还不太清的小伙伴,可以去 vue 官方文档 看一下 vue mixins 的基本概念和用法。

mixins 实现

那 mixins 是如何实现的呢 ?当 vue 在实例化的时候,会调用 mergeOptions 函数进行 options 的合并,函数申明在 vue/src/core/util/options.js 文件。

export function mergeOptions(
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  ...
  // 如果有 child.extends 递归调用 mergeOptions 实现属性拷贝
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  // 如果有 child.mixins 递归调用 mergeOptions 实现属性拷贝
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  // 申明 options 空对象,用来保存属性拷贝结果
  const options = {}
  let key
  // 遍历 parent 对象,调用 mergeField 进行属性拷贝
  for (key in parent) {
    mergeField(key)
  }
  // 遍历 parent 对象,调用 mergeField 进行属性拷贝
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  // 属性拷贝实现方法
  function mergeField(key) {
    // 穿透赋值,默认为 defaultStrat
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

为了保持代码简洁,已经将 mergeOptions 函数不重要的代码删除,剩余部分我们慢慢来看。

const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}

首先申明 extendsFrom 变量保存 child.extends,如果 extendsFrom 为真,递归调用 mergeOptions 进行属性拷贝,并且将 merge 结果保存到 parent 变量。

if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}

如果 child.mixins 为真,循环 mixins 数组,递归调用 mergeOptions 实现属性拷贝,仍旧将 merge 结果保存到 parent 变量。

接下来是关于 parent、child 的属性赋值:

const options = {}
let key

for (key in parent) {
  mergeField(key)
}

for (key in child) {
  if (!hasOwn(parent, key)) {
    mergeField(key)
  }
}

申明 options 空对象,用来保存属性拷贝的结果,也作为递归调用 mergeOptions 的返回值。

这里首先会调用 for...in 对 parent 进行循环,在循环中不断调用 mergeField 函数。

接着调用 for...in 对 child 进行循环,这里有点不太一样,会调用 hasOwn 判断 parent 上是否有这个 key,如果没有再调用 mergeField 函数,这样避免了重复调用。

那么这个 mergeField 函数到底是用来做什么的呢?

function mergeField(key) {
  // 穿透赋值,默认为 defaultStrat
  const strat = strats[key] || defaultStrat
  options[key] = strat(parent[key], child[key], vm, key)
}

mergeField 函数接收一个 key,首先会申明 strat 变量,如果 strats[key] 为真,就将 strats[key] 赋值给 strat。

const strats = config.optionMergeStrategies
...
optionMergeStrategies: Object.create(null),
...

strats 其实就是 Object.create(null),Object.create 用来创建一个新对象,strats 默认是调用 Object.create(null) 生成的空对象。

顺便说一句,vue 也向外暴露了 Vue.config.optionMergeStrategies,可以实现自定义选项合并策略。

如果 strats[key] 为假,这里会用 || 做穿透赋值,将 defaultStrat 默认函数赋值给 strat。

const defaultStrat = function(parentVal: any, childVal: any): any {
  return childVal === undefined ? parentVal : childVal
}

defaultStrat 函数返回一个三元表达式,如果 childVal 为 undefined,返回 parentVal,否则返回 childVal,这里主要以 childVal 优先,这也是为什么有 component > mixins > extends 这样的优先级。

mergeField 函数最后会将调用 strat 的结果赋值给 options[key]。

mergeOptions 函数最后会 merge 所有 options、 mixins、 extends,并将 options 对象返回,然后再去实例化 vue。

钩子函数的合并

我们来看看钩子函数是怎么进行合并的。

function mergeHook(
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
      ? childVal
      : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

循环 LIFECYCLE_HOOKS 数组,不断调用 mergeHook 函数,将返回值赋值给 strats[hook]。

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]

LIFECYCLE_HOOKS 就是申明的 vue 所有的钩子函数字符串。

mergeHook 函数会返回 3 层嵌套的三元表达式。

return childVal
  ? parentVal
    ? parentVal.concat(childVal)
    : Array.isArray(childVal)
    ? childVal
    : [childVal]
  : parentVal

第一层,如果 childVal 为真,返回第二层三元表达式,如果为假,返回 parentVal。

第二层,如果 parentVal 为真,返回 parentVal 和 childVal 合并后的数组,如果 parentVal 为假,返回第三层三元表达式。

第三层,如果 childVal 是数组,返回 childVal,否则将 childVal 包装成数组返回。

new Vue({
  created: [
    function() {
      console.log('冲冲冲!')
    },
    function() {
      console.log('鸭鸭鸭!')
    }
  ]
})
// => 冲冲冲!
// => 鸭鸭鸭!

项目实践

使用 vue 的小伙伴们,当然也少不了在项目中使用 element-ui。比如使用 Table 表格的时候,免不了申明 tableData、total、pageSize 一些 Table 表格、Pagination 分页需要的参数。

我们可以将重复的 data、methods 写在一个 tableMixin 中。

export default {
  data() {
    return {
      total: 0,
      pageNo: 1,
      pageSize: 10,
      tableData: [],
      loading: false
    }
  },

  created() {
    this.searchData()
  },

  methods: {
    // 预申明,防止报错
    searchData() {},

    handleSizeChange(size) {
      this.pageSize = size
      this.searchData()
    },

    handleCurrentChange(page) {
      this.pageNo = page
      this.searchData()
    },

    handleSearchData() {
      this.pageNo = 1
      this.searchData()
    }
  }
}

当我们需要使用时直接引入即可:

import tableMixin from './tableMixin'

export default {
  ...
  mixins: [tableMixin],
  methods: {
    searchData() {
      ...
    }
  }
}

我们在组件内会重新申明 searchData 方法。类似这种 methods 对象形式的 key,如果 key 相同,组件内的 key 会覆盖 tableMixin 中的 key。

当然我们也可以在 mixins 中嵌套 mixins,申明 axiosMixin:

import tableMixin from './tableMixin'

export default {
  mixins: [tableMixin],

  methods: {
    handleFetch(url) {
      const { pageNo, pageSize } = this
      this.loading = true

      this.axios({
        method: 'post',
        url,
        data: {
          ...this.params,
          pageNo,
          pageSize
        }
      })
        .then(({ data = [] }) => {
          this.tableData = data
          this.loading = false
        })
        .catch(error => {
          this.loading = false
        })
    }
  }
}

引入 axiosMixin:

import axiosMixin from './axiosMixin'

export default {
  ...
  mixins: [axiosMixin],
  created() {
    this.handleFetch('/user/12345')
  }
}

在 axios 中,我们可以预先处理 axios 的 success、error 的后续调用,是不是少写了很多代码。

extend

顺便讲一下 extend,与 mixins 相似,只能传入一个 options 对象,并且 mixins 的优先级比较高,会覆盖 extend 同名 key 值。

// 如果有 child.extends 递归调用 mergeOptions 实现属性拷贝
const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}
// 如果有 child.mixins 递归调用 mergeOptions 实现属性拷贝
if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}
// 如果有 child.extends 递归调用 mergeOptions 实现属性拷贝
const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}
// 如果有 child.mixins 递归调用 mergeOptions 实现属性拷贝
if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}

在 mergeOptions 函数中,会先对 extends 进行属性拷贝,然后再对 mixin 进行拷贝,在调用 mergeField 函数的时候会优先取 child 的 key。

虽然 extends 的同名 key 会被 mixins 的覆盖,但是 extends 是优先执行的。

总结

注意一下 vue 中 mixins 的优先级,component > mixins > extends。

我们暂且将 mixins 称作是组件模块化,灵活运用组件模块化,可以将组件内的重复代码提取出来,实现代码复用,也使我们的代码更加清晰,效率也大大提高。

当然,mixins 还有更加神奇的操作等你去探索。

以上就是vue使用mixins优化组件的详细内容,更多关于vue 用mixins优化组件的资料请关注我们其它相关文章!

(0)

相关推荐

  • 讨论vue中混入mixin的应用

    混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能.一个混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被"混合"进入该组件本身的选项. 即 mixin 在引入组件之后,会将组件内部的内容如data.method等属性与父组件相应内容进行合并.相当于在引入后,父组件的各种属性方法都被扩充了. 比如在两个不同的组件的组件中调用sayHi方法,需要重复定义,倘若方法比较复杂,代码将更加冗余,但使用mixins 就相对比较简单了. 首先

  • 使用vue中的混入mixin优化表单验证插件问题

    这段时间开发的时候使用到了这个表单校验插件,用起来比较麻烦就不说了,还有较严重的缺陷.自己最终还是抽空把这个插件进行了一些优化,虽然优化的方式和当初自己定下的方向不怎么相同,但是就使用体验上来说还是提高了很多. 1. 实际开发中遇到的问题 在上面的文章中提及了一些表单插件的问题,主要针对的是插件开发本身存在的问题.下面我总结下使用中存在的问题: 每个使用表单校验的元素都需要添加 v-check 类名,比较麻烦. 必须在提交表单按钮上使用 v-checkSubmit 指令进行表单校验,提交函数必须

  • Vue使用mixin分发组件的可复用功能

    vue创建高阶组件的实现不够react优雅,但那是vue和react的设计思想导致的.在react中一切都是函数,而在vue中,组件最终都是函数,但在开发时可以是JSON对象,而且每个vue组件要注意三个点:props.events和slots,就是这三个导致vue创建高阶组件时要传入相应的属性,较react要复杂. vue官方推荐使用mixins来完成高阶组件的功能,如果对vue实现高阶组件有兴趣的话推荐看[vue实现高阶组件][1] 下面是vue官网使用mixins的例子: // 定义一个混

  • Vue之Mixins(混入)的使用方法

    混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能.一个混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被"混合"进入该组件本身的选项. 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行"合并". 比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先. var Mymixin = { data: function(){ return { message: 'hello', foo: 'ab

  • 深入了解Vue.js 混入(mixins)

    混入 (mixins)定义了一部分可复用的方法或者计算属性.混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项. 来看一个简单的实例: var vm = new Vue({ el: '#databinding', data: { }, methods : { }, }); // 定义一个混入对象 var myMixin = { created: function () { this.startmixin() }, methods: { startmix

  • 记一次Vue.js混入mixin的使用(分权限管理页面)

    需求背景:在一个后台的管理系统中,不同的用户角色对应不同的用户权限.现要求,同一个页面对有操作权限的用户来说是可操作的,对无操作权限的用户来说是只读的,即操作按钮均失效.系统用Vue.js开发. 一.mixin的概念 官方文档这么说:混入是一种分发Vue组件中可服用功能的非常灵活的方式.混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项. 我自己的理解:混入对象具有Vue组件可以声明的所有选项,如[components].[computed].[met

  • 详解Vue的mixin策略

    我之前一直以为mixin的合并是以组件内的优先,即mixin的内容如果和组件内有冲突的,以组件内为准,确实存在这种情况,但是vue指定的策略更详细,下面分别记录各种情况对应的合并策略 基本 当一个组件使用mixin的时候,所有mixin的选项会被混入到组件自己的选项中, 这部分没什么好说的,直接看代码 // define a mixin object const myMixin = { created() { this.hello() }, methods: { hello() { consol

  • 深入浅析Vue中mixin和extend的区别和使用场景

    Vue中有两个较为高级的静态方法mixin和extend,接下来我们来讨论下关于他们各自的原理和使用场景. Mixin: 原理: 先来看看官网的介绍: 参数:{Object} mixin 用法: 混入也可以进行全局注册.使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例.使用恰当时,这可以用来为自定义选项注入处理逻辑. // 为自定义的选项 'myOption' 注入一个处理器. Vue.mixin({ created: function () { var myOption

  • Vue混入mixins滚动触底的方法

    前言 在app端常常看到类似加载数据的动画,接下来我们来实现滚动触底加载动画提示,以及如何复用这些逻辑. 如何判断滚动触底 来看下几张图: 情况一: 当文档高度还为超过可视区域高度时,不存在滚动,所以也没有滚动触底 情况二: 当文档高度超过可视区域的高度时,还有剩余的文档没有滚动完,也就是说 可视区域高度 + 滚动高度 < 文档高度 ,此时没有达到滚动触底的条件   情况三: 文档高度大于可视区域,并且滚动到文档底部, 也就是说 可视区域高度 + 滚动高度 = 文档高度 判断是否滚动到底 经过上

  • Vue用mixin合并重复代码的实现

    在我们做项目的时候,往往有很多代码逻辑是通用的,比如说,业务逻辑类型的判断,时间戳的转换,url中字符串的截取等等,这些函数如果在每个需要的页面中都加入的话,不仅加重了当前页面的逻辑复杂程度,还会占用大量原本可以省略的内存.因此,将这些代码整理出来统一管理是很有必要的,在vue项目中,我们都知道模块化和组件化,但vue的框架中还有一个很好用的知识点,就是mixin. mixin不仅可以存放data.watch.methods.computed等,还可以存放Vue的生命周期,是不是很神奇呢? 通过

随机推荐