饮冰十年-人工智能-Vue3-67-组件间数据交互
很久以前我对Vue2的组件间数据交互做过学习,兜兜转转再用Vue已经是Vue3版本。
Vue3组件间数据交互
1、准备工作
环境准备
功能介绍
- 头部组件(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>
源码解析
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>
源码解析
-
父组件通过 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>
源码解析
有了上面的基础,这个比较好理解,父组件通过 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>
源码解析
-
定义 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>
总结
2、全局事件总线版本
安装
npm install mitt
添加时间总线eventBus.js
import mitt from 'mitt'; const eventBus = mitt(); export default eventBus;
MyHeader、MyFooter 这两个源码保持不变
<template> <ul class="todo-main"> <!-- eventBus02 <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"> --> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"> </MyItem> </ul> </template> <script setup> import MyItem from './MyItem.vue'; // defineProps 是一个 Vue 3 的编译宏,用于在 <script setup> 中定义组件接收的 props。 const props = defineProps({ todos: Array, // todos: 一个数组,用于存储待办事项列表。 // checkTodo: Function, // eventBus02 注释掉一些 // deleteTodo: Function // eventBus02 注释掉一些 }); </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>
源码解析
-
去掉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> import eventBus from '../eventBus'; // eventBus01 导入事件总线 // 接收 props const props = defineProps({ todo: Object, // checkTodo: Function, eventBus02 注释掉一些 // deleteTodo: Function }) // 处理勾选事件 const handleCheck = (id) => { // eventBus03 通知父组件,修改 todo 的 done 状态 eventBus.emit('checkTodo', id); } // 处理删除事件 const handleTodo = (id) => { if (window.confirm('确定删除吗??')) { // eventBus04 通知父组件, eventBus.emit('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>
源码解析
-
// eventBus01 导入事件总线
-
import eventBus from '../eventBus';
-
-
// eventBus02 注释掉一些
-
// 接收 props const props = defineProps({ todo: Object, // checkTodo: Function, // deleteTodo: Function })
-
-
// eventBus 通知父组件
-
// 处理勾选事件 const handleCheck = (id) => { // eventBus03 通知父组件,修改 todo 的 done 状态 eventBus.emit('checkTodo', id); } // 处理删除事件 const handleTodo = (id) => { if (window.confirm('确定删除吗??')) { eventBus.emit('deleteTodo', id); } }
-
<script setup> import { ref, watch } from 'vue' import eventBus from './eventBus'; // eventBus01 导入事件总线 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 } ); // eventBus03 将方法注册到事件总线上 eventBus.on('checkTodo', checkTodo); eventBus.on('deleteTodo', deleteTodo); </script> <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!-- 在父组件中,你可以通过 @ 符号来监听子组件发出的事件 --> <MyHeader @addTodo="addTodo"></MyHeader> <!-- eventBus02 <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList> --> <MyList :todos="todos"></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>
源码解析
-
// eventBus01 导入事件总线
-
import eventBus from './eventBus';
-
-
// eventBus02 修改原来props传递方式
-
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>
-
<MyList :todos="todos"></MyList>
-
-
// eventBus03 将方法注册到事件总线上
-
eventBus.on('checkTodo', checkTodo); eventBus.on('deleteTodo', deleteTodo);
-