很久以前我对Vue2的组件间数据交互做过学习,兜兜转转再用Vue已经是Vue3版本。
Vue3组件间数据交互
1、准备工作
环境准备
使用 Vite 创建一个新的 Vue 3 项目功能介绍
该功能由APPVue+4个组件组成- 头部组件(MyHeader)
主要是一个input框,用于收集用户输入内容,
当用户输入完成,并按下enter键后,将数据添加到 MyList 列表组件中
- 列表组件(MyList)
- 任务组件(MyItem)
- 底部组件(MyFooter)
2、props emit 版本
<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add" /> </div> </template> <script setup> // 引入nanoid import { nanoid } from "nanoid"; // 定义 emits const emit = defineEmits(['addTodo']) const add = (e) => { // 判断用户是否输入了内容 if (e.target.value.trim().length === 0) { alert("输入的内容不能为空"); return; } // 将用户的输入,包装成为一个todo对象 const todoObj = { id: nanoid(), title: e.target.value, done: false, }; // 将todo对象传递给App组件 emit('addTodo', todoObj) // 清空用户的输入 e.target.value = ""; } </script> <style scoped> /*header*/ .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>MyHeader
源码解析
emit 是一个用于触发事件的方法,允许子组件向父组件发送消息。这是 Vue 的一种组件通信机制,通常用于子组件向父组件传递数据或通知父组件发生了某个事件。
作用和用法
-
事件传递: 当子组件需要通知父组件某个事件发生时,可以使用 emit 方法。例如,当用户在输入框中输入任务并按下回车键时,你想要将这个任务传递给父组件。
-
自定义事件: emit 允许你创建自定义事件,父组件可以通过监听这些事件来响应子组件的行为。例如,在示例中,子组件使用 emit('addTodo', todoObj) 来触发名为 addTodo 的事件,并将 todoObj 作为参数传递给父组件。
如何在父组件中监听事件
-
在父组件中,你可以通过 @ 符号来监听子组件发出的事件
-
<MyHeader @addTodo="addTodo"></MyHeader>
<template> <ul class="todo-main"> <!-- v-for 指令用于遍历 todos 数组,生成多个 MyItem 组件。 :todo="todoObj": 将当前的 todoObj 传递给 MyItem 组件的 todo prop。 :checkTodo="checkTodo" 和 :deleteTodo="deleteTodo": 将父组件的方法 checkTodo 和 deleteTodo 作为 prop 传递给 MyItem 组件,允许子组件在适当的时候调用这些方法 --> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"> </MyItem> </ul> </template> <script setup> import MyItem from './MyItem.vue'; // defineProps 是一个 Vue 3 的编译宏,用于在 <script setup> 中定义组件接收的 props。 const props = defineProps({ todos: Array, // todos: 一个数组,用于存储待办事项列表。 checkTodo: Function, // checkTodo: 一个函数,用于处理勾选或取消勾选待办事项的操作。 deleteTodo: Function // deleteTodo: 一个函数,用于处理删除待办事项的操作。 }); </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>MyList.Vue
源码解析
-
父组件通过 props 向子组件传递数据和方法。
作用和用法
- 父组件传:父组件 APPVue 通过 : 符号绑定 todos、checkTodo 和 deleteTodo到子组件 MyList 组件。 Vue 会将这些数据和方法作为 props 传递给 MyList 组件。
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
-
子组件接:在 MyList.vue 组件中,使用 defineProps 来定义和接收这些 props:
<template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <span>{{ todo.title }}</span> </label> <button class="btn btn-danger" @click="handleTodo(todo.id)">删除</button> </li> </template> <script setup> // 接收 props const props = defineProps({ todo: Object, checkTodo: Function, deleteTodo: Function }) // 处理勾选事件 const handleCheck = (id) => { // 通知App组件,修改todo的done状态 props.checkTodo(id) } // 处理删除事件 const handleTodo = (id) => { if (window.confirm('确定删除吗?')) { props.deleteTodo(id) } } </script> <style scoped> /*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } li:hover { background-color: #ddd; } li:hover button { display: block; } </style>MyItem.vue
源码解析
有了上面的基础,这个比较好理解,父组件通过 props 向子组件传递数据和方法。不过不同的是,子组件在这里调用了props传的方法。
// 处理勾选事件 const handleCheck = (id) => { // 通知App组件,修改todo的done状态 props.checkTodo(id) } // 处理删除事件 const handleTodo = (id) => { if (window.confirm('确定删除吗?')) { props.deleteTodo(id) } }
<template> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" :checked="isAll" @change="checkAll" /> </label> <span> <span>已完成{{ doneTotal }}</span> / 全部{{ todos.length }} </span> <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> </div> </template> <script setup> import { computed} from 'vue' // 接收 props const props = defineProps({ todos: { type: Array, required: true } }); // 定义 emits const emit = defineEmits(['checkAllTodo', 'clearAllTodo']); // 计算属性 const total = computed(() => props.todos.length) const doneTotal = computed(() => { return props.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); }) const isAll = computed(() => doneTotal.value === total.value && total.value > 0) const checkAll = (e) => { emit('checkAllTodo', e.target.checked); } const clearAll = () => { emit('clearAllTodo') } </script> <style scoped> /*footer*/ .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>MyFooter.vue
源码解析
-
定义 props:
-
props 接收一个 todos 数组,表示所有的待办事项。
-
defineProps的时候可以为属性设置必填项
-
- 定义 emits:
-
-
emit 用于向父组件发送事件。定义了两个事件:checkAllTodo 和 clearAllTodo。
-
checkAll: 当复选框状态改变时,触发 checkAllTodo 事件,并传递复选框的勾选状态。
-
clearAll: 当点击清除按钮时,触发 clearAllTodo 事件。
-
<script setup> import { ref, watch } from 'vue' import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList .vue' import MyFooter from './components/MyFooter.vue' // 初始化 todos const todos = ref(JSON.parse(localStorage.getItem('todos')) || []) // 添加todo const addTodo = (todo) => { todos.value.unshift(todo); }; // 勾选或者取消勾选一个todo const checkTodo = (id) => { todos.value.forEach((todo) => { if (todo.id === id) { todo.done = !todo.done; } }); }; // 删除一个todo const deleteTodo = (id) => { todos.value = todos.value.filter((todo) => todo.id !== id); }; // 全选或者全不选 const checkAllTodo = (done) => { todos.value.forEach(todo => todo.done = done) }; // 清除所有已经完成的todo const clearAllTodo = () => { todos.value = todos.value.filter((todo) => !todo.done) } // 监听 todos 的变化并同步到 localStorage watch( todos, (newValue) => { localStorage.setItem('todos', JSON.stringify(newValue)); }, { deep: true } ); </script> <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 在父组件中,你可以通过 @ 符号来监听子组件发出的事件 --> <MyHeader @addTodo="addTodo"></MyHeader> <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"></MyFooter> </div> </div> </div> </template> <style scoped> .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.vue:hover { filter: drop-shadow(0 0 2em #42b883aa); } </style>APP.vue 这段源码大家可自行理解下。
总结
数据流: Vue 中的 props 实现了单向数据流,父组件通过 props 将数据和方法传递给子组件,子组件通过 emit 发送事件回父组件。 理解子组件中使用 emit 和直接通过 props 调用父组件方法的差异,可以通过一个简单的生活类比来解释。 类比:点餐和送餐 假设你在餐厅工作,负责点餐和送餐。餐厅有两个主要角色:服务员(父组件)和厨房(子组件)。 1. 使用 emit(服务员通知厨房) 服务员(父组件): 服务员记录顾客的点单(父组件的方法)。 服务员将点单传递给厨房(通过 emit 将事件传递给父组件)。 厨房(子组件): 厨房收到点单后,准备菜肴(子组件执行操作)。 厨房准备好菜肴后,通过铃声(emit 事件)通知服务员。 场景: 顾客点了一道菜,服务员记录并传递给厨房。 厨房准备好菜肴后,通过铃声通知服务员。 服务员听到铃声后,将菜肴送到顾客桌上。 解释: 服务员通过监听厨房的铃声(emit 事件)来知道菜肴已准备好,并将菜送到顾客桌上。 服务员没有直接去厨房检查菜肴是否准备好(不直接调用父组件的方法)。 2. 直接通过 props 调用(服务员自己做菜) 服务员(父组件): 服务员记录顾客的点单(父组件的方法)。 服务员直接自己准备菜肴(通过 props 直接调用父组件的方法)。 厨房(子组件): 厨房只是一个摆设(子组件只是传递数据),所有操作都由服务员完成。 场景: 顾客点了一道菜,服务员记录并自己准备菜肴。 服务员准备好菜肴后,直接将菜肴送到顾客桌上。 解释: 服务员没有依赖厨房的通知(emit 事件),而是自己完成所有步骤。 服务员直接根据点单准备菜肴(直接调用父组件的方法)。 对比 emit:像厨房通过铃声(事件)通知服务员菜肴已准备好。厨房不直接送菜,通知服务员由服务员完成最终的送菜动作。适用于子组件只需通知父组件某个事件发生,而不需要知道具体如何处理。 直接调用 props 方法:像服务员自己准备菜肴,不依赖厨房的通知。服务员直接完成点单记录和菜肴准备的所有步骤。适用于子组件需要直接调用父组件的方法来完成某些操作。2、全局事件总线版本
全局事件总线(Global Event Bus)全局事件总线是一个可以在应用的任何部分触发和监听事件的机制。 适用场景 它可以简化父子组件之间的通信,特别是当组件层级很深或者组件之间没有直接的父子关系时。 使用方法 在 Vue 3 中,不再推荐使用类似 Vue 2 中的 $bus 方式来创建全局事件总线 在 Vue 3 中,我们可以使用 mitt 这个库来创建一个简单的全局事件总线。mitt 是一个轻量级的事件总线库 安装npm install mitt
npm install mitt
添加时间总线eventBus.js
2、发布订阅模式
在 Vue 3 中,全局事件总线和发布-订阅模式是相似的概念,实际上事件总线就是一种实现发布-订阅模式的方式。 发布-订阅模式: 这是一种设计模式,允许对象之间的解耦。发布者(发布事件的组件)和订阅者(监听事件的组件)之间不直接交互,而是通过一个中介(事件总线)来通信。 发布者发布消息,订阅者通过监听特定的事件来响应这些消息。 事件总线: 事件总线是实现发布-订阅模式的一种具体方式。在 Vue 中,事件总线通常是一个独立的对象,它提供了 on(订阅事件)、emit(发布事件)和 off(取消订阅)等方法。 使用事件总线,组件可以轻松地发布和订阅事件,而无需了解彼此的存在。 总结 在 Vue 3 中,通过创建一个事件总线(如使用 mitt 库)实现组件之间的消息传递,实际上就是在应用发布-订阅模式。这使得组件之间的通信更加灵活和解耦,便于维护和扩展。 标签:Vue3,const,props,组件,事件,67,饮冰,todo,todos From: https://www.cnblogs.com/YK2012/p/18333300