首页 > 其他分享 >饮冰十年-人工智能-Vue3-67-组件间数据交互

饮冰十年-人工智能-Vue3-67-组件间数据交互

时间:2024-07-31 19:27:55浏览次数:11  
标签:Vue3 const props 组件 事件 67 饮冰 todo todos

上一篇:饮冰三年-人工智能-Vue-66 Vue组件化

  很久以前我对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

相关文章

  • 加油,为Vue3提供一个可媲美Angular的ioc容器
    为什么要为Vue3提供ioc容器Vue3因其出色的响应式系统,以及便利的功能特性,完全胜任大型业务系统的开发。但是,我们不仅要能做到,而且要做得更好。大型业务系统的关键就是解耦合,从而减缓shi山代码的生长。而ioc容器是目前最好的解耦合工具。Angular从一开始就引入了ioc容器,因此在业务......
  • vue3屏幕适配
    通过两个插件来实现  postcss-pxtorem和  amfe-flexible 在main.js中导入import"amfe-flexible";在vite.config.js中配置css:{postcss:{plugins:[postcssPxtoRem({rootValue:192,//根据使用的ui组件?定义根元素大小?......
  • 在Vue3中创建动态主题切换功能
    随着现代Web开发的进步,用户体验变得愈发重要。在这方面,实现动态主题切换功能无疑是提高用户体验的有效方式。通过动态主题切换,用户可以根据自己的喜好选择明亮的主题或暗色主题,提供了更个性化、更舒适的使用体验。今天,我们将通过一个简洁的示例来展示,如何在Vue3中实现动态......
  • P1967 [NOIP2013 提高组] 货车运输
    原题链接题解朴素做法:每次询问,二分最小边,然后bfs遍历查看是否能到达,时间复杂度\(O(q\cdotlogn\cdotm)\)优化:如果答案里的最小边是\(k\),那么代表所有边权不小于\(k\)的边都可以使用,因此可以直接从大到小加入边,直至起点与终点连接时间复杂度\(O(q\cdotm\cdotlogm......
  • Vue3 - 最新详细实现网站内部打开预览 office 全套附件,在页面弹窗内解析预览 word文档
    前言如果您需要Vue2版本,请访问这篇文章。在vue3|nuxt3项目开发中,详解实现项目内部“打开解析预览各种office文档”通用预览插件,支持弹出一个窗口在弹框内预览或者直接显示在页面某个div容器里面,解析预览word文档、excel电子表格、ppt演示文稿、pdf文档、txt文......
  • 这本vue3编译原理开源电子书,初中级前端竟然都能看懂
    前言众所周知vue提供了很多黑魔法,比如单文件组件(SFC)、指令、宏函数、cssscoped等。这些都是vue提供的开箱即用的功能,大家平时用这些黑魔法的时候有没有疑惑过一些疑问呢。我们每天写的vue代码一般都是写在*.vue文件中,但是浏览器却只认识html、css、js等文件类型,明显是不认......
  • [vue3] Vue3源码阅读笔记 reactivity - collectionHandlers
    源码位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/collectionHandlers.ts这个文件主要用于处理Set、Map、WeakSet、WeakMap类型的拦截。拦截是为了什么?为什么要处理这些方法?Vue3实现响应式的思路是使用ProxyAPI在getter中收集依赖,在setter触发更新......
  • [vue3] Vue3源码阅读笔记 reactivity - collectionHandlers
    源码位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/collectionHandlers.ts这个文件主要用于处理Set、Map、WeakSet、WeakMap类型的拦截。拦截是为了什么?为什么要处理这些方法?Vue3实现响应式的思路是使用ProxyAPI在getter中收集依赖,在setter触发更新......
  • Vue3实战案例 知识点全面 推荐收藏 超详细 及附知识点解读
    最近经常用到vue中的一些常用知识点,打算系统性的对vue3知识点进行总结,方便自己查看,另外也供正在学习vue3的同学参考,本案例基本包含Vue3所有的基本知识点,欢迎参考,有问题评论区留言,谢谢。本项目主要还是四个章节目录1.项目结构创建2.页面组件的划分3.页面组件实现3.1N......
  • 【Vue3】前端使用JWT令牌技术的实践方案
    目录技术介绍简单介绍:详细介绍:操作流程1.后端在登录响应里返回jwt2.前端将该变量存入浏览器当中3.前端使用jwt请求的时候作为请求头解码令牌内信息技术介绍简单介绍:        JWT令牌是一种用户校验机制,在登录后服务器会返回用户一个JWT令牌(相当于门票),用......