选项合并

当组件和混入对象含有同名选项时,Vue 会按照特定规则进行合并。理解这些规则对于正确使用混入至关重要。

数据合并

数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先:

const mixin = {
  data() {
    return {
      message: 'Hello from mixin',
      name: 'mixin'
    }
  }
}

new Vue({
  mixins: [mixin],
  data() {
    return {
      message: 'Hello from component',
      age: 18
    }
  },
  created() {
    console.log(this.message)
    console.log(this.name)
    console.log(this.age)
  }
})

输出:

Hello from component
mixin
18

组件的 message 覆盖了混入的 message,而 nameage 则被合并。

钩子函数合并

同名钩子函数将合并为一个数组,都会被调用,且混入的钩子先于组件钩子调用:

const mixin = {
  created() {
    console.log('mixin created')
  }
}

new Vue({
  mixins: [mixin],
  created() {
    console.log('component created')
  }
})

输出:

mixin created
component created

多个混入的钩子顺序

const mixin1 = {
  created() {
    console.log('mixin1 created')
  }
}

const mixin2 = {
  created() {
    console.log('mixin2 created')
  }
}

new Vue({
  mixins: [mixin1, mixin2],
  created() {
    console.log('component created')
  }
})

输出:

mixin1 created
mixin2 created
component created

方法合并

值为对象的选项,如 methods、components、directives 等,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对:

const mixin = {
  methods: {
    foo() {
      console.log('mixin foo')
    },
    bar() {
      console.log('mixin bar')
    }
  }
}

new Vue({
  mixins: [mixin],
  methods: {
    foo() {
      console.log('component foo')
    },
    baz() {
      console.log('component baz')
    }
  },
  mounted() {
    this.foo()
    this.bar()
    this.baz()
  }
})

输出:

component foo
mixin bar
component baz

组件的 foo 覆盖了混入的 foo,而 barbaz 则被合并。

计算属性合并

计算属性的合并规则与方法相同:

const mixin = {
  computed: {
    mixedComputed() {
      return 'from mixin'
    }
  }
}

new Vue({
  mixins: [mixin],
  computed: {
    mixedComputed() {
      return 'from component'
    },
    ownComputed() {
      return 'own property'
    }
  },
  created() {
    console.log(this.mixedComputed)
    console.log(this.ownComputed)
  }
})

输出:

from component
own property

监听器合并

watch 选项的合并策略与钩子函数类似,合并为数组:

const mixin = {
  watch: {
    value(newVal) {
      console.log('mixin watch:', newVal)
    }
  }
}

new Vue({
  mixins: [mixin],
  data() {
    return {
      value: 0
    }
  },
  watch: {
    value(newVal) {
      console.log('component watch:', newVal)
    }
  },
  mounted() {
    this.value = 1
  }
})

输出:

mixin watch: 1
component watch: 1

组件和指令合并

const mixin = {
  components: {
    MyButton: {
      template: '<button>Mixin Button</button>'
    }
  },
  directives: {
    'mixin-focus': {
      inserted(el) {
        el.focus()
      }
    }
  }
}

new Vue({
  mixins: [mixin],
  components: {
    MyButton: {
      template: '<button>Component Button</button>'
    }
  },
  directives: {
    'component-focus': {
      inserted(el) {
        el.focus()
      }
    }
  }
})

合并策略总结

选项类型合并策略冲突处理
data递归合并组件优先
钩子函数合并为数组都执行,混入先
methods合并为对象组件优先
computed合并为对象组件优先
watch合并为数组都执行,混入先
components合并为对象组件优先
directives合并为对象组件优先

完整示例

const mixin = {
  data() {
    return {
      message: 'mixin message',
      count: 0
    }
  },
  
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  
  methods: {
    increment() {
      this.count++
    },
    greet() {
      console.log('mixin greet')
    }
  },
  
  watch: {
    count(newVal) {
      console.log('mixin watch count:', newVal)
    }
  },
  
  created() {
    console.log('mixin created')
  }
}

new Vue({
  mixins: [mixin],
  
  data() {
    return {
      message: 'component message',
      name: 'Vue'
    }
  },
  
  computed: {
    tripleCount() {
      return this.count * 3
    }
  },
  
  methods: {
    greet() {
      console.log('component greet')
    }
  },
  
  watch: {
    count(newVal) {
      console.log('component watch count:', newVal)
    }
  },
  
  created() {
    console.log('component created')
    console.log('message:', this.message)
    console.log('name:', this.name)
    console.log('count:', this.count)
    console.log('doubleCount:', this.doubleCount)
    console.log('tripleCount:', this.tripleCount)
    this.greet()
    this.increment()
  }
})

输出:

mixin created
component created
message: component message
name: Vue
count: 0
doubleCount: 0
tripleCount: 0
component greet
mixin watch count: 1
component watch count: 1

注意事项

  1. 数据冲突:组件数据会覆盖混入数据
  2. 钩子顺序:混入钩子先于组件钩子执行
  3. 方法覆盖:组件方法会覆盖混入方法
  4. 命名规范:使用前缀避免命名冲突

理解选项合并规则后,接下来学习全局混入。