拆分组件
博客首页一个文件写到底,模板、数据、交互全挤在一起,代码一多就难维护。组件化开发的核心是把页面拆成一个个"独立零件":每个组件只负责自己的模板与逻辑,组件间用 props 向下传数据、用 emit 向上抛事件。本节把博客首页拆成 Header、PostList、PostCard 等小组件。
为什么要拆分组件
- 复用:文章卡片在首页、详情页、归档页都能用,写一次处处引用。
- 可维护:每个组件只做一件事,改动不会误伤其它区域。
- 易调试:小组件输入输出清晰,可单独测试。 拆分思路很简单:页面 = 组件树,父子组件只通过 props 与 emit 交流,不互相读写内部状态。
父传子:props
props 是父组件写给子组件的"参数"。子组件用 defineProps 声明要接收的数据:
<script setup>
const props = defineProps({ post: { type: Object, required: true } })
</script>
<template>
<h3>{{ props.post.title }}</h3>
<p>{{ props.post.summary }}</p>
</template>
父组件传值:<PostCard :post="posts[0]" />,运行后页面显示首篇文章标题与摘要。:post 是 v-bind:post 的简写,表示传变量而非字符串。
props 还能声明类型与默认值,父组件传错类型时开发环境会给出警告:
const props = defineProps({
post: Object,
showSummary: { type: Boolean, default: true } // 不传默认 true
})
子传父:emit
数据流是单向的:props 只能父传子。子组件要通知父组件(如"点了阅读全文"),需用 emit 抛出自定义事件:
<script setup>
const emit = defineEmits(['open']) // 声明可抛出的事件名
function handleClick() {
emit('open', props.post.id) // 抛出事件并携带文章 id
}
</script>
<template>
<button @click="handleClick">阅读全文</button>
</template>
父组件用 @open 监听该事件:
<template>
<PostCard :post="post" @open="openArticle" />
</template>
<script setup>
function openArticle(id) {
console.log('打开文章:', id) // 输出:打开文章:3
}
</script>
点击按钮后,父组件的 openArticle 就收到了子组件传来的文章 id。
博客首页组件树实战
把首页搭成"数据在上、展示在下"的结构,App.vue 持有数据并分发:
<script setup>
import { ref } from 'vue'
import PostCard from './PostCard.vue'
const posts = ref([
{ id: 1, title: 'Vue3 组合式 API', summary: '……' },
{ id: 2, title: 'Pinia 状态管理', summary: '……' }
])
</script>
<template>
<h1>我的博客</h1>
<PostCard v-for="p in posts" :key="p.id"
:post="p" @open="openArticle" />
</template>
当列表逻辑变复杂时,可把 v-for 那一层抽成 PostList 组件,props/emit 用法一致,数据流依旧清晰。
小结:拆分组件就是让页面变成组件树,props 向下传数据、emit 向上报事件,状态单向流动、职责分明。下一节加入 vue-router,让 URL 变化时切换组件实现页面跳转。