不少刚学Vue2的小伙伴,一碰到组件就犯懵,组件到底是个啥?咋创建?传值咋弄?不同组件咋通信?别慌,这篇文章用问答形式,把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监听事件,步骤:
- 新建
bus.js
:import Vue from 'vue'; export default new Vue();
- 组件A触发事件:
bus.$emit('eventName', 数据)
- 组件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:组件实例创建完成,
data
、methods
已初始化,但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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。