1 学习指南
1.1 学习目标
认知层级
目标描述
对应章节
了解
掌握组件化开发思想与核心价值
2.1 组件化思想
掌握
组件注册、父子通信基础用法
3.1-3.2、4.1-4.2
熟练
高级组件特性与复杂场景应用
4.3-4.5、6.0
应用
独立完成中型项目组件拆分与实现
5.0 实战案例
1.2 前置知识
- 掌握 Vue 基础语法(模板、指令、响应式数据)
- 了解 ES6 + 语法(解构、箭头函数、模块化)
- 具备 HTML/CSS 基础
2 组件的简介
组件是 Vue.js 的核心概念,是可复用的 Vue 实例,相当于页面的 “功能积木”。将复杂页面拆分为独立、可复用的组件,能实现:
- 代码模块化:单个组件专注单一功能,维护成本降低
- 复用性提升:同一组件可在多个页面重复使用
- 协作高效:多人开发时可并行开发不同组件
组件的本质是 “独立的视图 + 逻辑单元”,类比现实中的 “乐高积木”—— 每个积木有固定功能(比如轮子、车身),组合起来形成完整模型(应用)。
3 组件的使用
3.1 组件注册方式
3.1.1 全局注册(全局可用)
// main.js import { createApp } from 'vue' import App from './App.vue' import GlobalComponent from './components/GlobalComponent.vue' const app = createApp(App) // 全局注册:所有组件均可直接使用 app.component('GlobalComponent', GlobalComponent) app.mount('#app')3.1.2 局部注册(仅当前组件可用)
<LocalComponent /> 局部组件只能在当前父组件使用 --> > // 局部注册:导入即注册,无需额外配置 import LocalComponent from './LocalComponent.vue' </script>3.2 组件使用规范
注册方式对比
注册方式 | 适用场景 | 优点 | 缺点 | 最佳实践 |
全局注册 | 通用基础组件(如 Button/Icon) | 全局可直接使用,无需导入 | 增加打包体积,未使用组件无法 Tree-Shaking | 注册不超过 5 个核心基础组件 |
局部注册 | 业务组件(如 TodoInput/TodoItem) | 按需导入,减小打包体积 | 需手动导入后使用 | 业务模块优先使用,配合自动导入插件 |
- 组件名建议使用kebab-case(短横线分隔),如todo-item
- 单文件组件(SFC)统一后缀为.vue,包含<template>/<script>/` 三部分
- 样式隔离:在标签添加scoped` 属性,避免样式污染
4 组件之间的通信
组件通信是组件协作的核心,Vue3 提供了多种通信方案,重点关注父子组件通信。
4.1 父组件向子组件通信(已完成)
核心方案:Props 传递(单向数据流:父传子,子不可直接修改 props)
Parent.vue --> > :username="userName" :age="userAge" /> <script setup> import { ref } from 'vue' import ChildComponent from './ChildComponent.vue' const userName = ref('张三') const userAge = ref(18) </script> 组件 ChildComponent.vue --> <template> <p>姓名:{{ username }}</p> <p>年龄:{{ age }}</p> > // 定义props并指定类型、默认值、必填项 const props = defineProps({ username: { type: String, required: true }, age: { type: Number, default: 18 // 未传递时使用默认值 } }) </script>4.2 子组件向父组件通信
核心方案:自定义事件($emit),类比 “儿子向父亲汇报情况”,子组件触发事件,父组件监听并处理。
4.2.1 基础用法(3 步实现)
- 子组件声明可触发的事件(defineEmits)
- 子组件触发事件(emit('事件名', 数据))
- 父组件监听事件(@事件名="处理函数")
<!-- 子组件 ChildComponent.vue --> ="sendMessage">向父组件发送消息 ="inputValue" placeholder="输入要传递的内容" /> <script setup> import { ref } from 'vue' // 1. 声明可触发的事件(支持多个,数组形式) const emit = defineEmits(['message-sent', 'value-change']) const inputValue = ref('') // 2. 触发事件:第一个参数是事件名,后续是传递的数据 function sendMessage() { emit('message-sent', '子组件问候:你好,父组件!') // 传递字符串 emit('value-change', inputValue.value) // 传递输入框值 } .vue --> @message-sent="handleMessage" @value-change="handleValueChange" /> 的消息:{{ parentMessage }}</p> <p>父组件接收的输入值:{{ parentValue }}</p> > import { ref } from 'vue' import ChildComponent from './ChildComponent.vue' const parentMessage = ref('') const parentValue = ref('') // 3. 监听事件并处理数据 function handleMessage(msg) { parentMessage.value = msg } function handleValueChange(val) { parentValue.value = val } #### 4.2.2 进阶用法:v-model双向绑定(语法糖) `v-model`是`props + emit`的组合,适用于“父子组件数据同步”场景(如自定义输入框): ```vue <!-- 子组件 CustomInput.vue --> :value="modelValue" @input="emit('update:modelValue', $event.target.value)" /> 接收父组件v-model传递的value(默认名modelValue) defineProps(['modelValue']) // 声明更新事件(固定格式:update:modelValue) const emit = defineEmits(['update:modelValue']) .vue --> :Value="searchText" @update:modelValue="searchText = $event" /> --> v-model="searchText" /> 搜索内容:{{ searchText }}</p> > import { ref } from 'vue' import CustomInput from './CustomInput.vue' const searchText = ref('')4.2.2 注意事项
- 事件名建议使用kebab-case(短横线分隔),与组件名规范一致
- 可传递任意类型数据(字符串、对象、数组等)
- 子组件不可直接修改父组件数据,必须通过 “触发事件” 让父组件自行修改(遵循单向数据流)
5 组件实战案例:TodoList 任务清单
5.1 案例需求
实现一个简易 TodoList,包含功能:
- 父组件管理任务列表(数据存储)
- 子组件 1(TodoInput):输入任务并添加到列表(子传父)
- 子组件 2(TodoItem):展示单个任务,支持删除功能(子传父)
- 父组件向子组件传递任务数据(父传子)
5.2 代码实现
5.2.1 目录结构
src/ ├── components/ │ ├── TodoInput.vue // 任务输入组件(子) │ └── TodoItem.vue // 单个任务组件(子) └── App.vue // 父组件(任务列表管理)5.2.2 子组件 1:TodoInput.vue(添加任务)
-input"> v-model="newTodo" placeholder="请输入新任务" @keyup.enter="addTodo" /> click="addTodo">添加</button> </div> { ref } from 'vue' const emit = defineEmits(['add-todo']) // 声明添加事件 const newTodo = ref('') function addTodo() { if (newTodo.value.trim()) { // 传递任务对象给父组件 emit('add-todo', { id: Date.now(), // 用时间戳作为唯一ID content: newTodo.value, done: false }) newTodo.value = '' // 清空输入框 } } <style scoped> .todo-input { margin: 20px 0; display: flex; gap: 10px; } input { flex: 1; padding: 8px; } button { padding: 8px 16px; background: #42b983; color: white; border: none; border-radius: 4px; cursor: pointer; } </style>5.2.3 子组件 2:TodoItem.vue(单个任务)
<template> class="todo-item"> done: todo.done }" @click="toggleDone"> {{ todo.content }} ="deleteTodo">删除</button> > <script setup> const props = defineProps({ todo: { type: Object, required: true, // 验证todo对象结构 validator: (value) => { return 'id' in value && 'content' in value && 'done' in value } } }) const emit = defineEmits(['delete-todo', 'toggle-done']) // 触发删除事件(传递任务ID) function deleteTodo() { emit('delete-todo', props.todo.id) } // 触发状态切换事件 function toggleDone() { emit('toggle-done', props.todo.id) } </script> oped> .todo-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #eee; } .done { text-decoration: line-through; color: #999; } button { background: #ff4444; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; }5.2.4 父组件:App.vue(核心管理)
<template> 1>TodoList 任务清单 :输入任务(子传父:添加任务) --> add-todo="handleAddTodo" /> 子组件2:展示任务(父传子:任务数据;子传父:删除/切换) --> todo-list"> <TodoItem v-for="todo in todoList" :key="todo.id" :todo="todo" @delete-todo="handleDeleteTodo" @toggle-done="handleToggleDone" /> </div> > import { ref } from 'vue' import TodoInput from './components/TodoInput.vue' import TodoItem from './components/TodoItem.vue' // 父组件维护任务列表(响应式数据) const todoList = ref([ { id: 1, content: '学习Vue组件通信', done: false }, { id: 2, content: '完成实战案例', done: false } ]) // 处理添加任务(接收子组件传递的任务对象) function handleAddTodo(newTodo) { todoList.value.push(newTodo) } // 处理删除任务(接收子组件传递的任务ID) function handleDeleteTodo(todoId) { todoList.value = todoList.value.filter(todo => todo.id !== todoId) } // 处理切换任务状态(接收子组件传递的任务ID) function handleToggleDone(todoId) { const todo = todoList.value.find(todo => todo.id === todoId) if (todo) todo.done = !todo.done } </script> scoped> h1 { color: #333; } .todo-list { border: 1px solid #eee; border-radius: 4px; padding: 10px; }5.3 案例效果
- 输入任务内容,点击“添加”或按回车,任务会添加到列表(子传父)
- 点击任务文本,切换完成状态(子传父触发状态更新)
- 点击“删除”按钮,移除对应任务(子传父触发删除)
- 父组件通过props将任务数据传递给`TodoItem`组件(父传子)
5.4 案例核心知识点总结
1. 组件拆分:按功能拆分为“输入组件”“列表项组件”“父组件”,职责单一
2. 父传子:通过props传递任务数据、配置项
3. 子传父:通过自定义事件传递操作(添加/删除/切换)和数据(任务ID/对象)
4. 响应式数据:父组件维护核心数据,子组件仅负责展示和触发事件
如果需要补充某部分细节(如 TypeScript 支持、组件复用技巧),或想扩展其他实战案例(如购物车、表单组件),可以随时告诉我!