1-4 组件化:props / emit / slot
配套 Demo
组件是 Vue 的核心思想:把界面拆成一个个可复用、可组合的小块。本章讲组件之间怎么传数据、怎么通信。
后端类比:组件 ≈ 函数/类。
props≈ 入参,emit≈ 回调/事件通知,slot≈ 模板里的「占位插槽」。
配套 demo:
demo/1-4-components.html。
1. 为什么要组件化
一个页面如果几百行写在一起,没法维护也没法复用。组件化让你:
- 复用:一个「商品卡片」组件,列表里用 100 次。
- 拆分:把大页面拆成 Header、List、Item、Footer,各管各的。
- 隔离:每个组件有自己的数据和逻辑,互不干扰。
2. 定义和使用一个组件(CDN 写法)
// 定义一个组件 = 一个对象(和 createApp 里那个对象长得一样)
const ProductCard = {
props: ['name', 'price'], // 声明它接收哪些参数
template: `
<div class="card">
<h3>{{ name }}</h3>
<p>¥{{ price }}</p>
</div>
`
};
const app = createApp({ /* 根组件 */ });
app.component('ProductCard', ProductCard); // 全局注册
app.mount('#app');用的时候像普通标签:
<ProductCard name="键盘" :price="60" />
<ProductCard name="鼠标" :price="30" />真实项目里组件写在
.vue文件里(第 2 部分),这里用 CDN 的template字符串方式先理解概念。
3. props——父传子(入参)
父组件把数据传给子组件,就靠 props。
子组件声明要接收什么:
const ProductCard = {
props: ['name', 'price'], // 简单写法
template: `<div>{{ name }} - ¥{{ price }}</div>`
};更规范的写法(带类型和默认值,推荐):
props: {
name: { type: String, required: true },
price: { type: Number, default: 0 },
}父组件传值:
<!-- 传字符串字面量:直接写 -->
<ProductCard name="键盘" />
<!-- 传变量/数字/表达式:要用 :(v-bind),否则会当字符串 -->
<ProductCard :name="item.name" :price="item.price" />⚠️ 注意区别:name="price" 传的是字符串 "price";:price="60" 传的是数字 60。传非字符串一定要加 :。
重要原则:props 是单向、只读的
子组件不能直接修改 props(改了 Vue 会警告)。数据从父流向子,子要改就通过 emit 通知父(下面讲)。
后端类比:props 像传进来的「值参数」,你在方法里不应该去改调用方的对象。要影响外部,通过返回值/回调。
4. emit——子传父(事件通知)
子组件想告诉父组件「我发生了某件事」(比如按钮被点了),用 emit 抛一个事件,父组件监听。
子组件:声明并触发事件
const ProductCard = {
props: ['name', 'price'],
emits: ['add'], // 声明会抛出的事件
setup(props, { emit }) {
function handleAdd() {
emit('add', props.name); // 抛出 add 事件,带上数据
}
return { handleAdd };
},
template: `<button @click="handleAdd">加入购物车</button>`
};父组件:用 @ 监听
<ProductCard name="键盘" @add="onAdd" />function onAdd(name) {
console.log('父组件收到:加入了', name);
}数据流总结:props 往下传(父→子),emit 往上抛(子→父)。这是 Vue 通信的基本盘,记牢这张图:
父组件 ├── props ↓(传数据给子) └── @event ↑(监听子抛上来的事件) 子组件 emit('event', 数据)
自定义组件的 v-model(进阶)
v-model 在组件上 = 传一个 modelValue prop + 监听 update:modelValue 事件。先了解,第 2 部分封装表单组件会用到。
5. slot——插槽(把内容塞进组件)
props 传的是「数据」,slot 传的是「一段模板/HTML」。让组件的某块内容由使用者决定。
子组件:留一个插槽占位
const Card = {
template: `
<div class="card">
<div class="card-body">
<slot></slot> <!-- 这里会被父组件传入的内容填充 -->
</div>
</div>
`
};父组件:在标签内部写内容
<Card>
<h3>任意标题</h3>
<p>任意内容,甚至别的组件</p>
</Card><Card> 标签之间的东西,会被塞进 <slot> 的位置。
后端类比:slot 像「模板方法模式」里留给子类填的钩子,或像传一段「片段」进去渲染。
具名插槽(多个插槽)
template: `
<div class="card">
<header><slot name="title"></slot></header>
<main><slot></slot></main>
</div>
`<Card>
<template #title><h3>标题区</h3></template>
正文区(默认插槽)
</Card>动手练习(用 demo/1-4-components.html)
demo 把购物车拆成了组件:ProductCard(子)+ 根组件(父):
- 观察根组件如何用
v-for渲染多个ProductCard,并通过:传 props。 - 点卡片上的「加入购物车」,看父组件顶部的购物车数量增加(emit 通信)。
- 看
Panel组件如何用 slot 把标题和内容交给父组件决定。 - 试着给
ProductCard加一个 propsdesc(描述)并显示出来。
小结
- 组件 = 可复用的界面单元,像函数/类。
- props:父→子传数据,子组件只读不可改。
- emit:子→父抛事件通知,父用
@监听。 - slot:父把一段模板内容塞进子组件。
- 通信口诀:props 往下,emit 往上,slot 塞内容。
➡️ 下一章:1-5 生命周期钩子