当前位置: 首页 > news >正文

scoped+组件通信+props校验+记事本组件

一、scoped作用及原理

1. scoped 的作用

1> 默认写在任意一个 .vue 文件中 写在 style 中的样式是全局样式, 这样的话, 不仅会影响当前组件, 也会影响其他组件

2> 全局样式: 会影响每个 vue 文件, 存在样式污染/冲突问题 style 不添加 scoped, 默认就是全局.

     局部样式: 只会针对当前组件中的标签失效, 不会影响其他组件的标签, 如果想让样式变成局部样式, 那么需要给style加上 scoped 属性(全局->局部)

比如下面的案例, 我把scope去除后, 需求是只让左边字体变红, 但是发现左边对h2标签的操作污染了右侧, 说明对左侧的h2是全局样式

加上scoped, 发现针对的真实左侧字体进行修改, 并且样式变成了局部样式

3> 作用: 防止不同组件(vue文件) 样式污染/冲突. 推荐给每个vue都加上scoped

2. scoped 的原理

当给组件的style添加了scoped属性之后, 组件会发生如下变化

1> scoped 会给当前组件内的所有标签添加一个自定义属性, 名为 data-v-xxxxxxxx(8位随机字符串)

2> scoped 会用这个属性选择器(上面的data-v-xxxxxxxx)配合我们编写的选择器会形成一个交集选择器  比如刚刚的h2.[class]{}    

注意: 每个组件只要添加scoped, 都会生成一个唯一的 data-v-xxxxxxxx 自定义属性(相当于一个唯一的标识), 换句话说, 每个组件生成的这个属性名都不同, 因此形成的交集选择器只能选中当前组件内的标签, 所以就保证了样式不会污染的问题.

源码后续会形成一个交集选择器

二、父子组件通信

1. 组件通信的介绍

1> 什么是组件通信: 一个组件把数据传递给另一个组件

2> 为什么需要组件通信: 以前把代码写在一起, 数据可以直接使用; 而由于今后开发vue3的项目是借助了组件化开发的思想, 不会把代码写在同一个文件中, 而是拆分成一系列的组件, 进而通过组件的组装, 拼装成完整的页面, 还要实现和以前一样的功能, 这里难免需要进行组件间的数据传递, 那么就需要组件通信了.(简而言之就是各个组件保持独立的同时保持通信)

  •  每个组件时一个独立的模块, 这个组件的数据别的组件无法使用
  •  别的组件需要用到当前组件的数据, 那么就需要组件通信了

3> 如何进行组件通信: 需要辨别俩个组件的关系, 进而选择不同的通信方案

  • 父子组件通信: (重点)

        父传子: props 自定义属性

        子传父: emit 自定义事件

  • 非父子组件通信:

        祖先和后代: provide() + inject()

        兄弟组件: eventBuss(事件总线)

        跨组建通信方案: Pinia(状态管理)

4> 如何辨别俩个组件的关系

  • 父子关系: 谁被使用, 谁就是子组件, 当前调用其他组件的组件就是父组件 
  • 兄弟关系: 俩个组件时平级关系

 

或者我们看vue调试工具

  • 爷孙关系: 存在父调用子,子调用孙的调用关系

下面MyRight和App就是爷孙关系,通过父亲MyLeft联系

具体代码:

App.vue

<script setup>
//导入组件
import MyLeft from './component/MyLeft.vue';</script>
<template><div class="container"><my-left></my-left></div>
</template>
<style>
.container {display: flex;
}
</style>

MyLeft.vue

<script setup>
import MyRight from './MyRight.vue';
</script>
<template> <div class="my-left" style="flex: 1;height: 100px;align-items: center;text-align: center;background: palegoldenrod;"><h2>我是左侧</h2></div><my-right></my-right>
</template><style scoped>
h2{color: red;
}
</style>

MyRight.vue

<script setup>
</script>
<template><div class="my-right" style="flex: 1;height: 100px;align-items: center;text-align: center;background: purple;"><h2>我是右侧</h2></div>
</template>
<style scoped>
h2{color: green;
}
</style>

2. 父传子 

概念:

父组件通过 props 把数据传递给子组件

什么时候需要父传子?

当子组件的数据不确定的时候(不是静态的, 子组件需要根据父组件的需求而变化)

步骤:

1> 子组件通过 defineProps() 接收自己需要的数据,进而使用数据(子接)

2> 父组件内,子组件的自定义标签上,通过自定义属性传递数据(父传)

整体流程

其实就相当于函数的调用, 我定义一个函数,然后定义几个形参, 我调用方再通过调用函数, 把实参传给函数, 然后函数处理完数据后把返回值再返回给调用方.

3. 配合循环独立传值

v-for遍历组件, 每次循环独⽴传值

我们把刚刚的goods对象写成一个数组, 然后使用v-for遍历数组,给我们的子组件进行传参,形成多个不同的商品

父组件: App.vue

<script setup>
//导入 MyGoods 商品组件import MyGoods from './component/MyGoods.vue'
// 提供数据
const goodsList = [{id: '4001172',name: '称⼼如意⼿摇咖啡磨⾖机咖啡⾖研磨机',price: 289,picture:'https://tse3-mm.cn.bing.net/th/id/OIP-C.JCD2kzvzP00dSraFX_NKeAHaHa?w=183&h=183&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001594',name: '⽇式⿊陶功夫茶组双侧把茶具礼盒装',price: 288,picture:'https://tse1-mm.cn.bing.net/th/id/OIP-C.AOCBulpuOkzlWClLPhab9AHaHa?w=159&h=180&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001009',name: '⽵制⼲泡茶盘正⽅形沥⽔茶台品茶盘',price: 109,picture:'https://tse3-mm.cn.bing.net/th/id/OIP-C.zx0eDuySFaVwiLnAPTpupgHaHa?w=203&h=203&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001874',name: '古法温酒汝瓷酒具套装⽩酒杯莲花温酒器',price: 488,picture:'https://tse2-mm.cn.bing.net/th/id/OIP-C.uU32xt4IIVtCI2wRuicBpQHaJV?w=174&h=220&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001649',name: '⼤师监制⻰泉⻘瓷茶叶罐',price: 139,picture:'https://tse4-mm.cn.bing.net/th/id/OIP-C.0y2MhpQW_M7fwL523BqbsQHaHa?w=183&h=183&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '3997185',name: '与众不同的⼝感汝瓷⽩酒杯套组1壶4杯',price: 108,picture:'https://tse4-mm.cn.bing.net/th/id/OIP-C.NriTJYmUBrVPy9zYHaXa1AHaHa?w=187&h=188&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '3997403',name: '⼿⼯吹制更厚实⽩酒杯壶套装6壶6杯',price: 100,picture:'https://tse1-mm.cn.bing.net/th/id/OIP-C.wvt53HLH75XUYw2ggvUY5QAAAA?w=187&h=187&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '3998274',name: '德国百年⼯艺⾼端⽔晶玻璃红酒杯2⽀装',price: 139,picture: 'https://tse1-mm.cn.bing.net/th/id/OIP-C.pEhn4i9VACd2AacXzozESQHaHY?w=188&h=187&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'}
]
</script>
<template><div class="list"><!-- 生成多个MyGoods 此时我们使用v-for 需要注意的是组件接收了数据,要给组件传值--><MyGoodsv-for="item in goodsList":key="item.id":imgUrl = "item.picture":title="item.title":price="item.price"></MyGoods></div>
</template>
<style lang="scss">
* {margin: 0;padding: 0;box-sizing: border-box;
}.list {width: 1000px;margin: 0 auto;display: flex;flex-wrap: wrap;
}
</style>

子组件 MyGoods.vue 

<script setup>
//什么时候需要父传子? 当子组件的数据不确定(不是静态的,需要根据父组件的需求而变化)
//父传子的语法:
//1. 子组件通过 defineProps() 接收自己需要的数据,进而使用数据(子接)
//2. 父组件内, 子组件的自定义标签上, 通过自定义属性传递数据(父传)
defineProps(['imgUrl', 'title', 'price'])
</script>
<template><div><div class="item"><img :src="imgUrl" /><p class="name">{{ title }}</p><p class="price">{{ price }}</p></div></div>
</template><style scoped lang="scss">
.item {width: 240px;margin-left: 10px;padding: 20px 30px;transition: all 0.5s;margin-bottom: 20px;.item:nth-child(4n) {margin-left: 0;}&:hover {box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);transform: translate3d(0, -4px, 0);cursor: pointer;}img {width: 100%;}.name {font-size: 18px;margin-bottom: 10px;color: #666;}.price {font-size: 22px;color: firebrick;}.price::before {content: '¥';font-size: 22px;}
}
</style>

运行结果

总结父传子

1. 作用: 解决组件数据不确定问题, 也就是说, 今后封装子组件的时候, 如果数据不能写死, 就需要用到父传子

2. 语法和步骤

1> 子接

2> 父传

3. 组件可以配合 v-for 等指令使用, 注意给组件独立传值

4. 组件的好处: 各自独立, 便于复用

4. 子传父 

为什么需要子传父?

我们通过一个需求来引入

1. 需求: 在刚刚的商品列表里面,每个商品增加一个砍价功能, 点击一下就能够让价格降低

2. 问题: 对props 会报警告, 效果出不来.

   原因: props(父传子的数据)是只读的,不能进行修改

3. 解决方案: 子传父, 它实际用来解决当子组件需要修改父组件数据的时候, 由于 父传子的 props 是只读的, 本质这个数据还是父组件的数据, 数据的操作权在父组件这里, 因此我们可以这样:

        1> 父组件提供修改数据的方法

        2> 子组件在恰当的时机, 通知父组件, 让父组件触发/调用父组件修改数据的 方法/函数

4. 子传父的语法/步骤

1> 父组件内, 子组件的自定义标签上, 绑定自定义事件, @自定义事件="父组件修改函数的数据"

2> 子组件在恰当的时机, 触发父组件自定义事件, 从而导致父组件修改数据函数的执行, 同时携带父组件函数需要的参数, emit('自定义事件',携带的参数...)

流程

 1. 父组件提供修改数据的函数,在组件内,子组件的自定义标签上,绑定自定义事件(父监听/绑定)

2. 子组件在恰当时机, 通知父组件, 调用 emit('自定义事件',携带的参数.)(子通知/触发)

总体流程: 子组件只是通知了父组件,然后把相应要用到的参数进行传入

具体的代码

App.vue

<script setup>
//导入 MyGoods 商品组件import MyGoods from './component/MyGoods.vue'
import { ref } from 'vue';
// 提供数据
const goodsList = ref([{id: '4001172',name: '称⼼如意⼿摇咖啡磨⾖机咖啡⾖研磨机',price: 289,picture:'https://tse3-mm.cn.bing.net/th/id/OIP-C.JCD2kzvzP00dSraFX_NKeAHaHa?w=183&h=183&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001594',name: '⽇式⿊陶功夫茶组双侧把茶具礼盒装',price: 288,picture:'https://tse1-mm.cn.bing.net/th/id/OIP-C.AOCBulpuOkzlWClLPhab9AHaHa?w=159&h=180&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001009',name: '⽵制⼲泡茶盘正⽅形沥⽔茶台品茶盘',price: 109,picture:'https://tse3-mm.cn.bing.net/th/id/OIP-C.zx0eDuySFaVwiLnAPTpupgHaHa?w=203&h=203&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001874',name: '古法温酒汝瓷酒具套装⽩酒杯莲花温酒器',price: 488,picture:'https://tse2-mm.cn.bing.net/th/id/OIP-C.uU32xt4IIVtCI2wRuicBpQHaJV?w=174&h=220&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '4001649',name: '⼤师监制⻰泉⻘瓷茶叶罐',price: 139,picture:'https://tse4-mm.cn.bing.net/th/id/OIP-C.0y2MhpQW_M7fwL523BqbsQHaHa?w=183&h=183&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '3997185',name: '与众不同的⼝感汝瓷⽩酒杯套组1壶4杯',price: 108,picture:'https://tse4-mm.cn.bing.net/th/id/OIP-C.NriTJYmUBrVPy9zYHaXa1AHaHa?w=187&h=188&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '3997403',name: '⼿⼯吹制更厚实⽩酒杯壶套装6壶6杯',price: 100,picture:'https://tse1-mm.cn.bing.net/th/id/OIP-C.wvt53HLH75XUYw2ggvUY5QAAAA?w=187&h=187&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'},{id: '3998274',name: '德国百年⼯艺⾼端⽔晶玻璃红酒杯2⽀装',price: 139,picture: 'https://tse1-mm.cn.bing.net/th/id/OIP-C.pEhn4i9VACd2AacXzozESQHaHY?w=188&h=187&c=7&r=0&o=7&cb=iwp&pid=1.7&rm=3'}
])
// index:下标, 让那个对象的price减少
// price: 每次减少的价格
//父组件提供一个修改数据的函数
const subPrice = (index, price) => {goodsList.value[index].price -= price;
}
</script>
<template><div class="list"><!-- 生成多个MyGoods 此时我们使用v-for 需要注意的是组件接收了数据,要给组件传值--><MyGoods v-for="(item,index) in goodsList" :key="item.id" :imgUrl="item.picture" :title="item.name" :price="item.price":idx="index"@ccc="subPrice"/></div>
</template>
<style lang="scss">
* {margin: 20px;padding: 0;box-sizing: border-box;
}.list {width: 1000px;margin: 0 auto;display: flex;flex-wrap: wrap;
}
</style>

MyGoods.vue

<script setup>
import { ref } from 'vue';
// 子传父
// 1. 父组件提供修改数据的函数,在组件内, 子组件的自定义标签上,绑定自定义事件(父监听/绑定)
// 2. 子组件在恰当时机, 通知父组件, 调用 emit('自定义事件',携带的参数.)(子通知/触发)
const props = defineProps(['imgUrl', 'title', 'price','idx'])// 拿到触发自定义事件的函数 emit
const emit = defineEmits()
const x = ref(3)
// 点击子组件砍价按钮
const onCut = ()=>{//这个ccc是父组件的自定义事件emit('ccc',props.idx,x.value)
}
</script>
<template><div><div class="item"><img :src="imgUrl" /><p class="name">{{ title }}</p><span>{{ price }}.00</span><!-- 点击子组件砍一刀按钮触发父组件修改数据的函数 --><button @click="onCut">砍一刀</button></div></div>
</template><style scoped lang="scss">
.item {width: 240px;margin-left: 10px;padding: 20px 30px;transition: all 0.5s;margin-bottom: 20px;.item:nth-child(4n) {margin-left: 0;}&:hover {box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);transform: translate3d(0, -4px, 0);cursor: pointer;}img {width: 100%;}.name {font-size: 18px;margin-bottom: 10px;color: #666;}.price {display: flex; align-items: center;height: 36px;font-size: 22px;color: firebrick;button{display: flex;margin: 50px;font-size: 14px;}}.price::before {content: '¥';font-size: 22px;}
}
</style>

 5. 父子通信总结

父传子: 父组件 -> 子组件

1. 什么时候需要父传子: 当子组件的数据不能写死的时候,通过父组件提供的数据来响应,从而提高组件的灵活性和复用性

2. 父传子: props自定义属性传递方案

props: 写在标签上的自定义属性, 例如

<MyGoods: imgUrl = "item.picrure....>

这些都是props,因为都是自己取的名字, 所以称之为自定义属性. 一个组件标签行可以添加任意个数, 任意类型的props,前提是需要在组件内部使用 defineProps()接收, 只有接收了才可以传递过去

const props = defineProps(['imgUrl', 'title', 'price','idx'])

<MyGoods v-for="(item,index) in goodsList"

        :key="item.id"

        :imgUrl="item.picture"

        :title="item.name"

        :price="item.price"

        :idx="index"

        />

步骤:

1> 子接(defineProps(["自定义属性名1","自定义属性名2"...])

2> 父传(自定义属性名="值:")

子传父: 子组件 -> 父组件

1. 什么时候需要子传父: 当子组件需要修改父组件数据的时候, 因为 props 是只读的, 只能看, 不能改.

2. 子传父: 自定义事件传递

        1> 父监听/绑定: @自定义事件 = "父修改数据的函数"

        2> 子触发/通知: emit('自定义事件',传递的参数..)

三、props校验

1. 为什么要校验?

提高代码的健壮性, 也就是子组件接收的是什么样的props, 那么父组件在传递的时候也必须按照这个规则来传递, 双方都得遵守的一种协议.

2. 语法 简易写法校验

只校验类型,下面是俩种接收方式

1> 数组接收法: defineProps(['imgUrl','title','price'])

  • 优点: 简单, 上手快
  • 缺点: 无法对props做校验, 健壮性差

2> 对象接收法:

defineProps({

        title: String,

        imgUrl: String,

        price: Number

})

  • 优点: 可以对props做校验, 健壮性相对好一些
  • 缺点: 写法相对复杂

我们写一个进度条组件来演示

子组件: MyProgress.vue

父组件: App.vue

运行结果

3. 语法 完整写法校验

1> 为什么需要用到完整写法?

因为简易写法达不到需求, 比如刚刚的进度条组件, 当 width 不传的时候, 是能正常显示 50% 的进度, 这个需求简易写法做不到, 所以需要 props 更多的写法.

2> 完整写法的语法: 

defineProps({

    属性名: {

        type: 类型, // Number String Boolean ...

        required: true, // 是否必填

        default: 默认值, // 默认值// value: ⽗传⼦的值

        validator(value) {

            // ⾃定义校验逻辑

            return 布尔值

        }

    }

})

MyProgress.vue

App.vue

具体代码

App.vue

<script setup>
import MyProgress from './component/MyProgress.vue';	</script>
<template>
<!--我们不加v-bind传的是字符串,而接收的
是一个整型的数字  -->
<MyProgress :width="20"/>
<MyProgress :width="80"/>
<!-- 我们的需求是不写后面的,能不能显示进度条的百分比 --><!-- 当 width 不传的时候, 是能正常显示 50% 的进度 -->
<MyProgress/></template><style scoped></style>

 MyProgress.vue

<script setup>
defineProps({//只能接收一个名为 width 的属性 并且类型是Number//完整写法width: {type: Number,//约束类型default: 50,//默认值required: false//ture必传就不会使用默认值}
})
</script>
<template><div class="my-progress"><!-- 第一个width是css属性名是width,第二个width是我们上面自定义的width --><div class="inner" :style="{ width: width + '%' }"><span>{{ width }}%</span></div></div>
</template>
<style scoped>
.my-progress {height: 26px;width: 400px;border-radius: 15px;background-color: #272425;border: 3px solid #272425;box-sizing: border-box;margin-bottom: 30px;
}.inner {position: relative;background: #379bff;border-radius: 15px;height: 25px;box-sizing: border-box;left: -3px;top: -2px;
}.inner span {position: absolute;right: -30px;top: 26px;
}
</style>

4. 组件的 ref/reactive 数据 和 props 的区别

1> 相同点: 都可以为组件提供数据, 进而使用

2> 不同点: 

  • ref/reactive数据: 是组件自己的数据, 组件既可以获取, 也可以修改(可读可写)
  • props数据: 本质是由父组件提供的数据, 子组件拿到这个数据之后, 只能获取, 不能修改(只读的), 遵循单向数据流原则.(单向数据流: 子组件的props数据是来源于父组件的, 当父组件的数据修改了, 会影响子组件的props,这个方向只能是父->子流向. 父->子)如果子组件想要修改父组件的数据, 需要用到子传父的通信规则(通过emit)
  • computed数据: 基于现有的数据计算得到新数据, 带缓存的特性.
  • watch: 监视数据的变化, 当数据变了, 可以针对的做DOM操作或异步操作.

总结一句话: 谁的数据,谁负责

四、记事本组件版

拆分成 3 个组件, TodoHeader、TodoMain、TodoFooter, 如下图所⽰

添加操作: 当按下enter后, 就会在数组里面增加一个对象(header)

删除操作: 点击x号按钮, 去除数组的响应对象(main)

底部功能以及持久化存储(footer)

1> 底部我们需要统计有多少项是未完成的

直接计算数组对象里面属性为finished然后取反即可

2> 点击clear completed 清空整个完成的任务项

3> 实现数据持久化: 每次刷新,当前的数据状态能够保证

分析: 每次我们刷新后从父组件传过来给Main组件的的数组都是默认的数组,而我们当前的需求是每次刷新引用的数组是上一次的值,而不是用默认的数组

也就是我们每次要把main里面的todolist的值放在application里面的localstorage进行存储,后面刷新就取出里面的值进行渲染.  

此时修改todolist会涉及3个操作, 因此我们要对这三个地方进行监视 

具体步骤

总体代码

app.vue

<script setup>
import { ref, watch } from 'vue'
// 导入全局样式
import './assets/style.css'
// 导入 3 个子组件
import TodoFooter from './component/TodoFooter.vue';
import TodoHeader from './component/TodoHeader.vue';
import TodoMain from './component/TodoMain.vue';
// 定义常量, 本地存储的键名
const TODO_LIST_KEY = 'todo-list-key'
//待办任务列表
//有值就用之前的值从localstorage取出来,并且进行反序列化, 没有值就取默认
const todoList = ref(JSON.parse(localStorage.getItem(TODO_LIST_KEY)) || [{ id: 321, name: '学Vue组件通信', finished: false },{ id: 127, name: '打王者', finished: true },{ id: 666, name: '跑步1⼩时', finished: false }
])
//添加
const addTodo = (name) => {todoList.value.push({id: Date.now(),name,finished: false})
}
//删除
const delTodo = (i) => {//确认提示if (window.confirm('确认删除吗?')) {//调用splic(i,1)从当前点击的位置删一个todoList.value.splice(i, 1)}
}
// 清除所有已完成
const clearTodo = () => {//调用数组的 filter(),筛选出 所有 finished为false//再重新赋值给 todoList.valuetodoList.value = todoList.value.filter((item) => item.finished === false)}
//监视数据的变化: 3种操作会触发 todoList的变化
//分别是: 添加,删除,切换选中状态
watch(todoList, (newVale) => {//数组中,每个对象的属性发生变化后,watch也能监视到todolist的变化,此时需要深度监视// console.log('todolist变了', newVale)//把 todoList 最新的值序列化之后存储到本地localStorage.setItem(TODO_LIST_KEY, JSON.stringify(newVale))
},{deep: true}
)
</script>
<template><div class="todoapp"><!-- 第一个参数是子组件自定义的属性, 第二个参数是我们要赋的值 --><!-- 头部 --><todo-header @add-todo="addTodo" /><!-- 主体 --><todo-main :todos="todoList" @del-todo="delTodo" /><!-- 尾部 --><todo-footer :todoList="todoList" @clear-todo="clearTodo" /></div>
</template><style scoped></style>

ToFooter.vue

<script setup>
import { computed} from 'vue'
//子接收
const props = defineProps({todoList:{type: Array,default: ()=>[]}
})
console.log(props)
//基于接收到的数组筛选出未完成的 
const unFinishedLength =  computed(()=>{// 记录finished为false的return props.todoList.filter((item)=>!item.finished).length
})
//触发自定义事件的函数,通知父组件清除所有已完成任务
const emit = defineEmits()</script>
<template><footer class="footer"><span class="todo-count"><strong>{{ unFinishedLength }}</strong> item left</span><ul class="filters"><li><a href="#/" class="selected">All</a></li><li><a href="#/active">Active</a></li><li><a href="#/completed">Completed</a></li></ul><button class="clear-completed" @click="emit('clear-todo')">Clear completed</button></footer>
</template>

TodoHeader.vue

<script setup>
//拿到输入框的值
import {ref} from 'vue'
//收集输入框的值
const emit = defineEmits()
const title = ref('')
//当回车按下
const onEnter = () => {// 非空校验if(!title.value) return alert('任务名称不能为空')// 触发父组件的自定义事件emit('add-todo',title.value)// 清空输入框title.value = ''
}</script>
<template><header class="header"><h1>记事本</h1><inputv-model.trim="title" @keydown.enter="onEnter"class="new-todo"placeholder="What needs to be finished?" autofocus/></header>
</template>

ToMain.vue

<!-- @format --><script setup>const props = defineProps({// 接收的数据名称  todostodos: {type: Array, // 数组类型default: () => [] // 默认值}})const emit = defineEmits()
</script><template><section class="main"><inputid="toggle-all"class="toggle-all"type="checkbox" /><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><liv-for="(item, index) in props.todos":key="item.id":class="{ completed: item.finished }"><div class="view"><inputv-model="item.finished"class="toggle"type="checkbox" /><label>{{ item.name }}</label><buttonclass="destroy"@click="emit('del-todo', index)"></button></div></li></ul></section>
</template>

五、非父子组件通信(实际用的不多,但是面试会考)

1. 祖先传后代-provide & inject

祖先组件给后代组件传值(通信):

1. 祖先组件提供数据: provide('数据名称',数据)

2. 后代组件获取数据: const 数据 = inject('数据名称')

整体流程

具体代码

孙子组件MyChild.vue

<script setup>
import { inject } from 'vue';
//获取祖先提供的数据
const money =  inject('qian')
</script><template><div class="my-child"><h3>MyChild组件</h3><p>收到祖先给的数据: {{ money }}</p></div>
</template><style scoped>
.my-child {padding: 20px;background: palegoldenrod;
}
</style>

父亲组件MyParent.vue

<script setup>
import MyChild from './MyChild.vue';
</script>
<template><div class="my-parent"><h3>MyParent组件</h3><MyChild/></div>
</template><style scoped>
.my-parent {padding: 20px;background: plum;
}
</style>

根组件: App.vue

<script setup>
import { provide,ref } from 'vue';
import MyParent from './component/MyParent.vue';	//祖先给孙子留下的赚钱小目标
const money = ref(12000000)
//祖先提供的数据
provide('qian',money)
</script>
<template><div class="app"><h3>我是App组件</h3><MyParent /></div>
</template><style scoped>
.app{padding: 20px;width: 400px;background: paleturquoise;
}
</style>

2. 任意俩个组件通信- EventBus

下面不能够使用父子和爷孙进行传递数据,此时就引出任意俩个组件之间进行通信

任意俩个组件通信: EventBus(事件总线)

以俩个兄弟组件通信为例子:

前提: 需要一个中间者(桥梁)

1. 创建一个中间者模块 -- 桥梁

2. 确定消息的发送方: 桥梁.emit('事件名称',数据)

3. 确定消息的接收方: 桥梁.on('事件名称',(数据)=>{

})

具体步骤

1. 下载 mitt 模块包,并创建公共中间⼈模块(类似媒婆和中介)
npm add mitt -S

具体的流程走向

代码:

MyLeft.vue

<script setup>
// 默认导入中间人
import brige from '../event.Bus.js'
import { ref } from 'vue'
//接收数据
const word = ref('')
//发送方进行发送消息
const msg = ref('你好,我想和你做朋友')
const onSend = () => {//发送信息brige.emit('hello', msg.value)
}   
brige.on('rsp',(str)=>{word.value = str
})</script>
<template><div class="my-left"><h3>我是左边</h3><button @click="onSend">给右边发信息</button><br/><p>收到回复: {{ word }}</p></div>
</template><style scoped>
.my-left {display: flex;justify-content: center;align-items: center;flex: 1;height: 100px;background: plum;color: #fff;
}
</style>

event.Bus.js

import mitt from "mitt";
// 创建桥梁
const brige = mitt()
// 默认导出: 目的给 MyLeft 和 MyRight 能够拿到brige中间人
export default brige

MyRight.vue

<script setup>
//默认导入中间人
import { ref } from 'vue'
import brige from '../event.Bus.js'
const str = ref('')
brige.on('hello', (msg) => {str.value = msg;//右边给左边发信息brige.emit('rsp', '你好啊小白,我愿意和你当朋友')
})  
</script>
<template><div class="my-right"><h3>我是右边</h3><br><p>收到左边的消息: {{ str }}</p></div>
</template><style scoped>
.my-right {display: flex;justify-content: center;align-items: center;flex: 1;height: 100px;background: paleturquoise;color: #fff;
}
</style>

App.vue

<script setup>
import MyLeft from './component/MyLeft.vue';
import MyRight from './component/MyRight.vue';
</script>
<template><div class="app"><MyLeft/><MyRight/></div>
</template><style scoped>
.app{display: flex;
}
</style>

六、总结

相关文章:

  • 企业微信PC端 开启调试模式
  • 关于我的服务器
  • Python + 淘宝 API 开发实战:自动化采集商品详情与 SKU 数据清洗指南
  • Qt项目——串口调试助手
  • vite【详解】常用配置 vite.config.js / vite.config.ts
  • 数据库案例2--事务、视图和索引
  • Flowable进阶-网关、事件和服务
  • cout和printf的区别
  • 量子噪声模拟器是验证量子算法的鲁棒性
  • LVGL Animation Image(Animimg)控件详解
  • [特殊字符] 第 2 篇:快速上手 Framer Motion(实操入门)
  • vue学习笔记06
  • NLM格式与温哥华格式的区别与联系是什么?
  • 基于STM32、HAL库的TCA8418RTWR I/O扩展器驱动程序设计
  • 【3D文件】3D打印迪迦奥特曼,3D打印的迪迦圣像,M78遗迹管理局,5款不同的3D打印迪迦免费下载,总有一款适合你
  • vue + element-plus自定义表单验证(修改密码业务)
  • MySQL安装实战:从零开始搭建你的数据库环境
  • ANDON系统如何解决重工业车间的信息传递难题
  • 基于django云平台的求职智能分析系统(源码+lw+部署文档+讲解),源码可白嫖!
  • 数据库day-05
  • 从香料到文化,跟着陈晓卿寻味厦门
  • 谁为金子疯狂:有人贷款十万博两千,有人不敢再贸然囤货
  • 新增1839个!2024年度本科专业备案和审批结果,公布
  • 国家疾控局局长沈洪兵:将逐步缩小国内免疫规划与国际差距
  • 锚定“水库不垮坝”目标,水利部部署今年水库安全度汛工作
  • 执政将满百日,特朗普政府面临国内“三重暴击”