Code前端首页关于Code前端联系我们

不少刚学Vue2的小伙伴,一碰到组件就犯懵,组件到底是个啥?咋创建?传值咋弄?不同组件咋通信?别慌,这篇文章用问答形式,把Vue2组件从基础到实战的知识点掰碎了讲,帮你彻底搞懂~

terry 8小时前 阅读数 11 #Vue
文章标签 Vue2 组件

啥是Vue2组件?为啥项目里非用不可?

Vue里的组件,其实就是可复用的Vue实例,能把页面拆成一个个独立又能组合的小模块,比如做电商网站,商品列表里每个“商品卡片”结构一样、数据不同,把卡片写成组件,重复用就特方便。

为啥必须用?想象下,整个页面逻辑全堆在一个Vue实例里,代码会乱成粥!用组件能让代码“模块化”——头部、侧边栏、商品卡片各管各的,维护时找对应组件就行,还能多人协作(你写头部、我写商品列表),而且组件复用能减少重复代码,效率拉满~

咋创建Vue2组件?全局和局部有啥区别?

Vue2里创建组件分全局注册局部注册两种方式:

  • 全局组件:用 Vue.component('组件名', { /* 选项 */ }) 注册,注册后整个项目任何地方都能直接用,比如全局注册一个 <MyButton> 组件:

    Vue.component('MyButton', {
      template: '<button>点击我</button>',
      data() { return { count: 0 } } // 注意data必须是函数!后面讲为啥
    })
  • 局部组件:在某个Vue实例的 components 选项里注册,只有当前实例能访问,比如在页面级Vue实例里注册:

    new Vue({
      el: '#app',
      components: {
        'MyCard': { /* 组件选项 */ }
      }
    })

区别很明显:全局组件适合项目通用模块(比如全局弹窗、按钮);局部组件更灵活,只在需要的页面用,减少全局污染~

组件里的data为啥必须是函数?踩过坑才懂!

这是Vue组件“复用性”逼的!假设组件 data 是对象,

const MyComp = {
  data: { count: 0 },
  template: '<button @click="count++">{{ count }}</button>'
}

当页面里用两次 <MyComp>,点击其中一个按钮,两个组件的count会一起变!因为对象是引用类型,多个组件实例共享同一份 data

改成函数就解决了:data() { return { count: 0 } },每次创建组件实例时,函数会返回新的对象副本,每个实例的 data 相互独立,点击时就只改自己的count啦~

父组件和子组件咋传值?props和$emit是关键!

Vue2里父→子用props,子→父用$emit,这套组合拳搞定父子通信:

父传子(props):

子组件先声明要接收的“属性”,比如子组件 ChildComp 要接收父组件的 msg

const ChildComp = {
  props: ['msg'], // 也能限制类型:props: { msg: String }
  template: '<div>{{ msg }}</div>'
}

父组件用属性传值:<ChildComp :msg="parentMsg"/>(注意 :msg 是v-bind,传变量;不用v-bind传字符串)。

子传父($emit):

子组件触发事件,把数据“抛”给父组件,比如子组件有个按钮,点击后通知父组件:

const ChildComp = {
  template: '<button @click="handleClick">点我</button>',
  methods: {
    handleClick() {
      this.$emit('sendData', '我是子组件数据'); // 触发sendData事件,传数据
    }
  }
}

父组件监听事件:<ChildComp @sendData="handleReceive"/>,然后在 methods 里写 handleReceive(data) { /* 处理data */ }

举个实际场景:父组件是“购物车页面”,子组件是“商品项”,父传子商品名称、价格;子组件点击“加入购物车”按钮,用 $emit 告诉父组件“要加这个商品”,父组件更新购物车列表~

非父子组件咋通信?事件总线、Vuex选哪个?

项目里不止父子组件,兄弟组件、跨多层的组件咋通信?两种常用方案:

事件总线(Event Bus):

原理是创建一个空的Vue实例当“中介”,组件A触发事件,组件B监听事件,步骤:

  1. 新建 bus.jsimport Vue from 'vue'; export default new Vue();
  2. 组件A触发事件:bus.$emit('eventName', 数据)
  3. 组件B监听事件:bus.$on('eventName', (data) => { /* 处理数据 */ })

适合小型项目,比如兄弟组件切换Tab时传状态,但组件多了,事件到处飞,维护起来头大~

Vuex(状态管理库):

适合中大型项目,多个组件共享复杂状态(比如用户信息、购物车数据),Vuex把数据存在“全局仓库”,任何组件都能取、改,核心概念有 state(存数据)、mutations(改数据,同步)、actions(异步操作)。

比如存用户信息:

const store = new Vuex.Store({
  state: { user: null },
  mutations: { 
    setUser(state, data) { state.user = data }
  }
})

组件里用 this.$store.state.user 取数据,用 this.$store.commit('setUser', 新数据) 改数据。

Vuex能让数据流向更清晰,但学习成本稍高,小项目没必要硬上~

组件生命周期钩子有啥用?实战场景举例!

组件从“创建”到“销毁”的过程中,Vue会触发一系列生命周期钩子,我们可以在特定阶段干特定事:

  • created:组件实例创建完成,datamethods 已初始化,但DOM还没渲染,适合发Ajax请求数据(比如列表组件刚创建,就请求接口拿列表数据)。
  • mounted:DOM已经渲染完成,能操作DOM了,适合初始化第三方插件(比如给轮播图组件初始化swiper插件)。
  • updated:组件数据更新后触发,适合数据变了后做DOM同步(比如表格数据更新后,重新计算列宽)。
  • beforeDestroy:组件销毁前触发,适合清除定时器、取消事件监听(比如组件里有 setInterval,销毁前得清掉,否则内存泄漏)。

举个完整例子:做一个“新闻列表”组件

  • created 里调接口,把新闻数据存到 data
  • mounted 里给列表项绑定点击事件(或初始化下拉刷新插件);
  • 数据更新时(比如用户点“刷新”),updated 里更新列表的滚动位置;
  • 组件被移除前(比如切换页面),beforeDestroy 里清除接口请求的防抖函数、定时器~

动态组件和异步组件是啥?性能优化靠它们!

这俩是Vue2里“灵活复用+性能优化”的神器:

动态组件:

<component :is="组件名"/> 实现根据数据切换不同组件,比如后台管理系统的“Tab切换”,点“订单”显示 <OrderComp>,点“商品”显示 <GoodsComp>,代码:

<component :is="currentComp"></component>
<script>
new Vue({
  data: { currentComp: 'OrderComp' },
  components: { OrderComp, GoodsComp }
})
</script>

异步组件:

把组件写成按需加载的工厂函数,页面初始化时不加载,用到时再加载,减少首屏体积,比如一个很大的“报表组件”,用户没点进报表页就不加载:

const ReportComp = () => import('./ReportComp.vue') // 异步加载
new Vue({
  components: { ReportComp }
})

打包时,这个组件会被单独拆成一个js文件,用户访问到对应功能时才加载,首屏加载速度起飞~

插槽(Slot)是干啥的?普通和作用域插槽咋玩?

插槽是让父组件能往子组件里塞自定义内容,子组件留“坑”,父组件填内容~

普通插槽:

子组件用 <slot> 留坑,父组件往坑里插内容,比如子组件 CardComp

<template>
  <div class="card">
    <slot>默认内容</slot> <!-- 没传内容时显示“默认内容” -->
  </div>
</template>

父组件用的时候插内容:

<CardComp>
  <h2>我是父组件插的标题</h2>
  <p>我是父组件插的描述</p>
</CardComp>

作用域插槽:

子组件给插槽传数据,父组件能拿到这些数据再自定义渲染,比如子组件 TableComp 要把每一行数据传给父组件:

<template>
  <table>
    <tr v-for="item in tableData" :key="item.id">
      <slot :row="item"></slot> <!-- 传当前行数据row -->
    </tr>
  </table>
</template>
<script>
export default {
  data() { return { tableData: [/* 表格数据 */] } }
}
</script>

父组件用 <template #default="slotProps"> 接收数据,然后自定义列:

<TableComp>
  <template #default="slotProps">
    <td>{{ slotProps.row.name }}</td>
    <td>{{ slotProps.row.price }}</td>
    <td><button @click="handleEdit(slotProps.row)">编辑</button></td>
  </template>
</TableComp>

作用域插槽特别适合“组件结构固定,但内容渲染逻辑交给父组件”的场景(比如表格、列表的列自定义)~

看完这些问题,是不是对Vue2组件从“懵圈”到“清晰”了?组件是Vue2工程化开发的核心,把这些基础玩明白,再去搞组件库、UI框架(比如Element UI)就顺多了~下次碰到组件通信、性能优化这些需求,也知道从哪儿下手啦~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门