混入基础

混入提供了一种将组件中可复用的功能提取出来的方式。理解混入的基础用法是掌握代码复用的关键。

基本用法

const myMixin = {
  data() {
    return {
      message: 'Hello from mixin'
    }
  },
  methods: {
    greet() {
      console.log(this.message)
    }
  }
}

new Vue({
  mixins: [myMixin],
  created() {
    this.greet()
  }
})

局部混入

在组件中使用 mixins 选项引入混入:

<div id="app">
  <button @click="greet">问候</button>
  <p>{{ message }}</p>
</div>

<script>
const greetMixin = {
  data() {
    return {
      message: 'Hello!'
    }
  },
  methods: {
    greet() {
      alert(this.message)
    }
  }
}

new Vue({
  el: '#app',
  mixins: [greetMixin]
})
</script>

多个混入

组件可以使用多个混入:

const mixin1 = {
  data() {
    return {
      name: 'mixin1'
    }
  }
}

const mixin2 = {
  data() {
    return {
      age: 18
    }
  }
}

new Vue({
  mixins: [mixin1, mixin2],
  created() {
    console.log(this.name)
    console.log(this.age)
  }
})

混入可以包含的选项

混入对象可以包含任何组件选项:

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

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

实际应用示例

鼠标位置追踪

const mouseTracker = {
  data() {
    return {
      mouseX: 0,
      mouseY: 0
    }
  },
  
  methods: {
    updateMousePosition(e) {
      this.mouseX = e.clientX
      this.mouseY = e.clientY
    }
  },
  
  mounted() {
    window.addEventListener('mousemove', this.updateMousePosition)
  },
  
  beforeDestroy() {
    window.removeEventListener('mousemove', this.updateMousePosition)
  }
}

new Vue({
  el: '#app',
  mixins: [mouseTracker],
  template: `
    <div>
      鼠标位置: {{ mouseX }}, {{ mouseY }}
    </div>
  `
})

窗口尺寸监听

const windowSize = {
  data() {
    return {
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight
    }
  },
  
  methods: {
    handleResize() {
      this.windowWidth = window.innerWidth
      this.windowHeight = window.innerHeight
    }
  },
  
  mounted() {
    window.addEventListener('resize', this.handleResize)
  },
  
  beforeDestroy() {
    window.removeEventListener('resize', this.handleResize)
  }
}

加载状态

const loadingMixin = {
  data() {
    return {
      loading: false
    }
  },
  
  methods: {
    startLoading() {
      this.loading = true
    },
    
    stopLoading() {
      this.loading = false
    },
    
    async withLoading(asyncFn) {
      this.startLoading()
      try {
        return await asyncFn()
      } finally {
        this.stopLoading()
      }
    }
  }
}

new Vue({
  mixins: [loadingMixin],
  methods: {
    async fetchData() {
      await this.withLoading(async () => {
        const response = await fetch('/api/data')
        this.data = await response.json()
      })
    }
  }
})

表单验证

const formValidationMixin = {
  data() {
    return {
      errors: {}
    }
  },
  
  methods: {
    validate(rules) {
      this.errors = {}
      let isValid = true
      
      for (const field in rules) {
        const value = this.form[field]
        const fieldRules = rules[field]
        
        for (const rule of fieldRules) {
          const result = rule(value)
          if (result !== true) {
            this.errors[field] = result
            isValid = false
            break
          }
        }
      }
      
      return isValid
    },
    
    hasError(field) {
      return !!this.errors[field]
    },
    
    getError(field) {
      return this.errors[field]
    }
  }
}

分页功能

const paginationMixin = {
  data() {
    return {
      currentPage: 1,
      pageSize: 10,
      total: 0
    }
  },
  
  computed: {
    totalPages() {
      return Math.ceil(this.total / this.pageSize)
    },
    
    hasNext() {
      return this.currentPage < this.totalPages
    },
    
    hasPrev() {
      return this.currentPage > 1
    }
  },
  
  methods: {
    goToPage(page) {
      this.currentPage = page
      this.onPageChange()
    },
    
    nextPage() {
      if (this.hasNext) {
        this.currentPage++
        this.onPageChange()
      }
    },
    
    prevPage() {
      if (this.hasPrev) {
        this.currentPage--
        this.onPageChange()
      }
    },
    
    onPageChange() {
      // 子组件实现
    }
  }
}

混入文件组织

将混入放在单独的文件中:

export const mouseTracker = {
  data() {
    return {
      mouseX: 0,
      mouseY: 0
    }
  },
  
  methods: {
    updateMousePosition(e) {
      this.mouseX = e.clientX
      this.mouseY = e.clientY
    }
  },
  
  mounted() {
    window.addEventListener('mousemove', this.updateMousePosition)
  },
  
  beforeDestroy() {
    window.removeEventListener('mousemove', this.updateMousePosition)
  }
}

在组件中导入:

<script>
import { mouseTracker } from '@/mixins/mouseTracker'

export default {
  mixins: [mouseTracker]
}
</script>

使用建议

适合使用混入的场景:

  • 跨组件的共享逻辑
  • 可复用的功能模块
  • 统一的生命周期处理

不适合使用混入的场景:

  • 复杂的状态管理(用 Vuex)
  • 大量共享的 UI(用组件)
  • 需要明确数据来源的场景

掌握混入基础后,接下来学习选项合并规则。