Vue 2 组件封装全面指南

 1. 组件基础定义

1.1 全局组件注册
 

Vue.component('my-component', {
  // 组件配置选项
  template: '<div>Hello Vue 2 Component</div>',
  data: function() {
    return {
      message: 'Hello'
    }
  }
})

 1.2 局部组件注册

new Vue({
  el: '#app',
  components: {
    'my-component': {
      template: '<div>Hello Local Component</div>',
      data: function() {
        return {}
      }
    }
  }
})

 2. 组件核心选项

2.1 data(必须是函数)

data: function() {
  return {
    counter: 0
  }
}

2.2 props(父子组件通信)

props: ['title', 'userId'],
// 带验证的props
props: {
  title: {
    type: String,
    required: true,
    default: 'Default Title'
  },
  userId: {
    type: Number,
    validator: function(value) {
      return value > 0
    }
  }
}

2.3 methods

methods: {
  handleClick: function(event) {
    this.counter++
    this.$emit('clicked', this.counter)
  }
}

2.4 computed

computed: {
  fullName: function() {
    return this.firstName + ' ' + this.lastName
  },
  // 带getter和setter的计算属性
  fullName: {
    get: function() {
      return this.firstName + ' ' + this.lastName
    },
    set: function(newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

2.5 watch

watch: {
  message: function(newValue, oldValue) {
    console.log('Message changed from', oldValue, 'to', newValue)
  },
  // 深度监听
  user: {
    handler: function(newUser) {
      console.log('User changed', newUser)
    },
    deep: true,
    immediate: true // 立即执行一次handler
  }
}

3. 组件通信方式

3.1 父组件向子组件传递数据 (props)

// 父组件
<template>
  <child-component :title="parentTitle" :user-id="userId"></child-component>
</template>

// 子组件
props: ['title', 'userId']

3.2 子组件向父组件通信 ($emit)

// 子组件
methods: {
  sendData: function() {
    this.$emit('data-sent', { message: 'Hello from child' })
  }
}

// 父组件
<template>
  <child-component @data-sent="handleDataReceived"></child-component>
</template>

methods: {
  handleDataReceived: function(data) {
    console.log(data.message) // 'Hello from child'
  }
}

3.3 非父子组件通信

3.3.1 事件总线 (EventBus)
// 创建事件总线
const EventBus = new Vue()

// 组件A发送事件
EventBus.$emit('custom-event', data)

// 组件B接收事件
EventBus.$on('custom-event', function(data) {
  console.log(data)
})

// 销毁事件监听
EventBus.$off('custom-event')
 3.3.2 Vuex (状态管理)
// store.js
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment: state => state.count++
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {
    doubleCount: state => state.count * 2
  }
})

// 在组件中使用
this.$store.commit('increment')
this.$store.dispatch('incrementAsync')

4. 组件插槽 (Slots)

4.1 基本插槽

// 子组件
Vue.component('my-component', {
  template: `
    <div>
      <h2>标题</h2>
      <slot></slot>
    </div>
  `
})

// 父组件使用
<my-component>
  <p>这是插槽内容</p>
</my-component>

4.2 命名插槽

// 子组件
Vue.component('my-layout', {
  template: `
    <div>
      <header><slot name="header"></slot></header>
      <main><slot></slot></main>
      <footer><slot name="footer"></slot></footer>
    </div>
  `
})

// 父组件使用
<my-layout>
  <template slot="header">
    <h1>页面标题</h1>
  </template>
  <p>主要内容</p>
  <template slot="footer">
    <p>页脚信息</p>
  </template>
</my-layout>

4.3 作用域插槽

// 子组件
Vue.component('my-list', {
  data: function() {
    return {
      items: ['item1', 'item2', 'item3']
    }
  },
  template: `
    <ul>
      <li v-for="item in items" :key="item">
        <slot :item="item" index="index"></slot>
      </li>
    </ul>
  `
})

// 父组件使用
<my-list>
  <template slot-scope="props">
    <span>{{ props.index }} - {{ props.item }}</span>
  </template>
</my-list>

5. 组件生命周期钩子

Vue.component('lifecycle-component', {
  beforeCreate: function() {
    // 实例初始化之后,数据观测和事件配置之前
  },
  created: function() {
    // 实例创建完成,属性已绑定,但DOM还未生成
  },
  beforeMount: function() {
    // 模板编译/挂载之前
  },
  mounted: function() {
    // 模板编译/挂载完成
  },
  beforeUpdate: function() {
    // 数据更新之前
  },
  updated: function() {
    // 数据更新完成
  },
  beforeDestroy: function() {
    // 实例销毁之前
  },
  destroyed: function() {
    // 实例销毁之后
  },
  // 激活和停用钩子(keep-alive组件专用)
  activated: function() {
    // keep-alive 组件激活时调用
  },
  deactivated: function() {
    // keep-alive 组件停用时调用
  }
})

 6. 高级组件封装技巧

 6.1 使用 mixins

// 定义一个mixin
const myMixin = {
  data: function() {
    return {
      mixinMessage: 'This is from mixin'
    }
  },
  methods: {
    mixinMethod: function() {
      console.log('Mixin method called')
    }
  }
}

// 在组件中使用
Vue.component('my-component', {
  mixins: [myMixin],
  mounted: function() {
    this.mixinMethod() // 调用mixin中的方法
  }
})

6.2 使用extends扩展组件

const baseComponent = {
  methods: {
    baseMethod: function() {
      console.log('Base method')
    }
  }
}

Vue.component('extended-component', {
  extends: baseComponent,
  mounted: function() {
    this.baseMethod() // 可以访问基类的方法
  }
})

6.3 动态组件

Vue.component('tab-a', { template: '<div>Tab A content</div>' })
Vue.component('tab-b', { template: '<div>Tab B content</div>' })

new Vue({
  el: '#dynamic-component',
  data: {
    currentTab: 'tab-a'
  }
})

// 模板中使用
<component v-bind:is="currentTab"></component>

6.4 异步组件

Vue.component('async-component', function(resolve, reject) {
  setTimeout(function() {
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})
// 或使用import()
Vue.component('async-component', () => import('./AsyncComponent.vue'))

 7. 组件封装最佳实践

 7.1 单一职责原则

  一个组件只负责一个功能,保持组件简洁。

7.2 组件状态管理

- 本地状态(仅组件内部使用):使用data
- 外部状态:使用props接收,$emit发送事件
- 全局状态:使用Vuex

7.3 组件复用性

- 定义清晰的props和事件接口
- 使用插槽增加灵活性
- 避免硬编码

7.4 性能优化

// 使用v-once缓存静态内容
<span v-once>{{ staticContent }}</span>

// 使用v-memo缓存计算结果
<div v-memo="[expensiveValue]">{{ expensiveComputation() }}</div>

// 避免不必要的watch深度监听

// 使用keep-alive缓存组件实例
<keep-alive>
  <component :is="currentTab"></component>
</keep-alive>

8. 组件高级API

 8.1 provide/inject (2.2.0+) - 依赖注入

// 父组件提供数据
provide: function() {
  return {
    theme: this.theme
  }
}

// 子组件注入数据
inject: ['theme']

8.2 程序化事件监听器

// 在组件中
this.$on('test', function(msg) {
  console.log(msg)
})
this.$once('test', function(msg) {
  // 只触发一次
  console.log(msg)
})

this.$emit('test', 'hello')

// 销毁所有事件监听
this.$off()
// 销毁特定事件
this.$off('test')

9. 组件样式隔离

9.1 Scoped CSS

<style scoped>
.my-class {
  color: red;
}
</style>

 9.2 CSS Modules

<style module>
.myClass {
  color: blue;
}
</style>

<template>
  <div :class="$style.myClass">Styled with CSS Modules</div>
</template>

Vue 2的组件封装是构建大型应用的基础,通过组合这些技术,可以创建可复用、易维护的组件库,提高开发效率和代码质量。

10.Vue2 组件生命周期补充说明

Vue2 组件的生命周期钩子函数分为创建、挂载、更新、销毁四个阶段。

以下是常见的钩子函数及其补充说明:

beforeCreate

在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。此时组件的 data 和 methods 还未初始化。

created

实例已经创建完成,数据观测 (data observer) 和 event/watcher 事件配置已完成。可以访问 data、computed、methods 等,但 DOM 还未生成,$el 不可用。

beforeMount

在挂载开始之前被调用,相关的 render 函数首次被调用。此时模板已编译完成,但尚未将 DOM 插入到页面中。

mounted

实例被挂载后调用,此时 DOM 已插入到页面中,可以访问到 $el。常用于执行需要 DOM 的操作,如初始化第三方库。

beforeUpdate

数据更新时调用,发生在虚拟 DOM 打补丁之前。可以在更新前访问现有的 DOM,如手动移除事件监听器。

updated

由于数据更改导致的虚拟 DOM 重新渲染和打补丁后调用。此时组件的 DOM 已经更新,可以执行依赖于新 DOM 的操作。

beforeDestroy

实例销毁之前调用。在这一步,实例仍然完全可用,适合进行清理工作,如清除定时器、取消事件监听等。

destroyed

实例销毁后调用。所有的事件监听器和子实例已被移除,所有指令已解绑。

生命周期钩子的使用场景

beforeCreate:适合插件开发时进行一些全局设置。

created:常用于发起异步请求获取数据。

mounted:适合需要操作 DOM 的场景,如图表初始化。

beforeUpdate:适合在更新前保存当前状态。

updated:适合在更新后执行依赖于新 DOM 的操作。

beforeDestroy:适合清理工作,避免内存泄漏。

父子组件生命周期执行顺序

父组件 beforeCreate 父组件 created 父组件 beforeMount 子组件 beforeCreate 子组件 created 子组件 beforeMount 子组件 mounted 父组件 mounted

注意事项

不要在 updated 钩子中修改状态,可能导致无限更新循环。

避免在 beforeCreate 中访问 data 和 methods,因为它们还未初始化。

异步请求建议放在 created 而非 mounted 中,以便更早获取数据。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐