组件拆分
一个组件干所有事,代码一长就难维护。组件拆分的核心思想是"各司其职、单向数据流":父组件提供数据与事件处理,子组件负责展示与触发事件。本节把任务系统拆成输入框、任务列表、单条任务、筛选栏四个组件。
拆分后的目录结构
在 src/components 下新建文件:
components/
├─ TaskInput.vue // 输入框与添加按钮
├─ TaskList.vue // 任务列表容器
├─ TaskItem.vue // 单条任务
└─ TaskFooter.vue // 筛选与统计
父子通信的两条通道
- props:父组件把数据"传下去",子组件用 defineProps 声明接收;
- emit:子组件把事件"抛上来",父组件用 @事件名 监听处理。
TaskItem:接收 props 并抛出事件
子组件不直接修改数据,只负责展示和发事件:
<script setup>
defineProps({ task: { type: Object, required: true } })
const emit = defineEmits(['toggle', 'remove'])
</script>
<template>
<li>
<input type="checkbox" :checked="task.done"
@change="emit('toggle', task.id)" />
<span :class="{ done: task.done }">{{ task.title }}</span>
<button @click="emit('remove', task.id)">删除</button>
</li>
</template>
TaskInput:只负责输入
同样通过 emit 把新任务标题交给父组件:
<script setup>
import { ref } from 'vue'
const title = ref('')
const emit = defineEmits(['add'])
function submit() {
if (!title.value.trim()) return
emit('add', title.value) // 把标题抛给父组件
title.value = ''
}
</script>
<template>
<input v-model="title" @keyup.enter="submit" placeholder="输入任务" />
<button @click="submit">添加</button>
</template>
App.vue:父组件统一管数据
数据只有一个来源,全部操作函数也放在这里:
<script setup>
import { ref } from 'vue'
import TaskInput from './components/TaskInput.vue'
import TaskList from './components/TaskList.vue'
const tasks = ref([]) // 唯一的任务数据源
const addTask = (title) => tasks.value.push({ id: Date.now(), title, done: false })
const removeTask = (id) => tasks.value = tasks.value.filter(t => t.id !== id)
const toggleTask = (id) => { // 找到任务并反转完成状态
const t = tasks.value.find(t => t.id === id)
if (t) t.done = !t.done
}
</script>
<template>
<TaskInput @add="addTask" />
<TaskList :tasks="tasks" @remove="removeTask" @toggle="toggleTask" />
</template>
TaskList:中间层做传递
列表负责用 v-for 渲染多个 TaskItem,并原样转发事件:
<script setup>
import TaskItem from './TaskItem.vue'
defineProps({ tasks: { type: Array, required: true } })
const emit = defineEmits(['remove', 'toggle'])
</script>
<template>
<ul>
<TaskItem v-for="task in tasks" :key="task.id"
:task="task"
@remove="emit('remove', $event)"
@toggle="emit('toggle', $event)" />
</ul>
</template>
效果:点击删除按钮,事件从 TaskItem → TaskList → App,App 改数据后视图自动更新,组件之间不再互相直接操作数据。
小结
拆分后每个组件只做一件事,代码更清晰;数据单向流动(props 下、emit 上)让 bug 更好定位。任务数据目前存在 App 里,下一节迁移到 Pinia 统一管理。