组件拆分

一个组件干所有事,代码一长就难维护。组件拆分的核心思想是"各司其职、单向数据流":父组件提供数据与事件处理,子组件负责展示与触发事件。本节把任务系统拆成输入框、任务列表、单条任务、筛选栏四个组件。

拆分后的目录结构

在 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 统一管理。

笔记加载中…