拆分组件

博客首页一个文件写到底,模板、数据、交互全挤在一起,代码一多就难维护。组件化开发的核心是把页面拆成一个个"独立零件":每个组件只负责自己的模板与逻辑,组件间用 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]" />,运行后页面显示首篇文章标题与摘要。:postv-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 变化时切换组件实现页面跳转。

笔记加载中…