vue2-组件通信

news/2024/11/25 7:44:33/

vue2-组件通信(非常重要)

1、props

props 父子组件间的通信,父给子传参

传参分为函数类型和非函数类型

  • 非函数类型

    • 基本数据类型: 子组件不能修改父组件传过来的数据
    • 引用数据类型: 子组件不能修改父组件传过来的引用数据类型地址,可以改里面的属性(但是不推荐,违反了单项数据流)
  • 函数数据类型

    传函数的目的是为了子组件调用修改父组件的数据

props接收方式(3种):

  • 数组

    props: ['count', 'userinfo', 'changeCount'],
    
  • 对象

    props: {count: Number,userinfo: Object,changeCount: Function
    },
    
  • 配置对象

    props: {count: {type: Number,required: true, // 必填default: 4 // 默认值// required 和 default 是互斥的,如果必填还设置默认值没有意义},userinfo: {type: Object,default () {return {name: '老六',age: 22}}},changeCount: {type: Function,required: true}
    },
    

2、自定义事件

实现子给父传参

事件分为 原生事件 和 自定义事件

  • 一、原生事件

    1. 事件类型 - click - 在原生事件中,事件类型一定是有限个数
    2. 触发机制 - 浏览器触发的,触发的时候会给函数传一个事件对象event,事件对象是浏览器生成的
  • 原生事件关于$event

    函数不加小括号 - 默认系统触发原生事件的时候,第一个参数是 事件对象

    函数加小括号 - 加小括号的时候,如果需要事件对象必须使用 $event (底层上是当前这个函数外部套了一层函数,套的这个函数形参是$event,我们用的就是这个形参)

  • 二、自定义事件 - 父子组件之间的通信,子给父传参

  1. 事件类型 - changeCount - 自定义事件是给组件绑定的,自己定义的,名字自己取,有无限个数

  2. 触发机制 - 自己触发的,参数需要自己传递,没有事件对象,使用$emit触发

    $emit('changeCount', 9)
    参数一:触发的事件类型  参数二: 传递的参数
    

    自定义事件关于$event

    函数不加小括号 - 触发的函数中的参数直接是 $emit 传过来的数据,可以传多个

    函数加小括号 - 自定义事件是没有事件对象的,$event变成了子组件传过来的参数,只能传一个

  • 一、原生事件

    • 标签绑定

      ​ 事件类型 click、mounseenter、mouseleave、keydown、keyup… 这些事件类型都是系统内置好的,一定是有限的

      ​ 触发机制 浏览器帮我们触发的

    • 组件绑定

      ​ 事件类型 click、mounseenter、mouseleave、keydown、keyup… 这些事件类型都是系统内置好的,一定是有限的

      ​ 触发机制 无法触发,会被理解城自定义事件

      ​ 如果非要把组件上的click事件变成原生的,那么需要给事件加修饰符 .native

      ​ 此时这个事件就绑定在子组件的根标签上

  • 二、自定义事件

    • 标签绑定

      ​ 事件类型 xxx … 自己起的名字,有无限个

      ​ 触发机制 没有意义,因为元素标签上绑定没有触发机制

    • 组件绑定

      ​ 事件类型 xxx … 自己起的名字,有无限个

      ​ 触发机制 自己绑定的事件,自己触发

      ​ 需要在子组件种使用 $emit(‘xxx’) 进行触发

  • $on、$off、$once

    $off 解绑事件用

    $on 绑定事件用,问: 已经有标签上使用v-on绑定事件,为什么要有$on呢?

    因为组件上使用 v-on 绑定同一个事件只能绑一次, 而使用$on 绑定一个事件可以绑多次

    -------------------------------------------------------

    关于参数:

    结论: $event 在原生事件当中是事件对象,在自定义事件中是参数(自己在$emit中传的参数)

3、全局事件总线

全局事件总线 - 跨组件之间的通信

  1. 安装总线

    new Vue({beforeCreate() {Vue.prototype.$bus = this; // 安装总线},
    }
    
  2. 接收数据

    在接收数据的组件中,绑定事件,留下回调,接收参数

    this.$bus.$on('receiveParams', this.receiveParams)

  3. 发送数据

    在发送数据的组件中,触发事件,传递参数

    this.$bus.$emit('receiveParams', 25)

4、v-model

实现:父子组件的数据同步

之前用在表单元素上,双向数据绑定,用来收集表达数据,现在可以给组件使用 v-model 指令,但是组件的 v-model 指令需要自己实现,步骤如下

步骤:

  1. v-model 用于表单数据的收集,是双向数据绑定,我们能不能不用 v-model 实现 input元素 双向数据绑定 ?

    <input type="text" :value="keyword" @input="keyword = $event.target.value">

  2. 步骤1中 使用了两个条件实现了表单 v-model 指令

    • 绑定value值
    • 触发input事件

    所以我们猜想:组件要想实现 v-model 指令,也需要实现这两个条件

  3. 组件调用

    <CustomInput :value="keyword" @input="keyword = $event"></CustomInput>

    组件实现

    <template><div class="box"><h3>子组件</h3><div>父组件传过来的keyword: {{ value }}</div><input type="text" :value="value" @input="changeValue"></div>
    </template><script>
    export default {name: "CustomInput",props: ['value'],methods: {changeValue(e) {this.$emit('input', e.target.value);}}
    };
    </script>
    

    在这个过程中,CustomInput 组件中的 input 内容发生改变,同时父组件的数据也会变化,数据具体的流向如下:

    1. 通过:value把数据绑定给子组件
    2. 子组件中使用props接收父组件传过来的数据
    3. 修改数据,使用$emit触发自定义事件,把参数传递给父组件
    4. 父组件接收到数据之后,更新keyword的值
    5. keyword值更新之后,又重新通过:value传递给子组件
  4. 我们已经在子组件中实现了这两个条件

    • 绑定value值
    • 触发input事件

    那么现在让我们把 CustomInput 组件刚刚绑定的这两个条件替换成 v-model 指令,发现真的可以使用 v-model 指令

    <CustomInput v-model="keyword"></CustomInput>
    

    结论:
    组件使用 v-model 条件: 必须实现两个条件 1. 绑定value值 2.绑定input事件
    什么情况下会使用v-model?
    封装组件的时候,组件中有表单元素的时候一般会使用 v-model
    在哪见过?
    在element ui 中的 el-input 组件使用的就是 v-model

5、sync

.sync 用于组件,父子组件间的数据同步

步骤:

  1. 不通过 v-model 能不能实现父子组件之间的数据同步呢?

    可以,代码尝试

    <Child1 :msg="string" @changeMsg="changeMsg"></Child1>
    

    Child1组件

    <template><div class="box"><h3>Child1</h3><div>父组件传过来的数据 msg: {{ msg }}</div><button @click="changeParentMsg">修改父组件传过来的数据</button></div>
    </template><script>
    export default {name: "Child1",props: ['msg'],methods: {changeParentMsg() {this.$emit('changeMsg', "我爱你,高圆圆")}}
    }
    </script>
    

    数据的流向:

    1. 通过:msg把数据传给子组件

    2. 子组件修改数据,$emit 触发了事件把参数传给父组件,父组件修改数据

    3. 父组件更改msg,再通过 :msg 传给子组件,进行展示

  2. 将实现的步骤1做一个小改动

    <Child1 :msg="string" @update:msg="changeMsg"></Child1>
    

    Child1组件

    <template><div class="box"><h3>Child1</h3><div>父组件传过来的数据 msg: {{ msg }}</div><button @click="changeParentMsg">修改父组件传过来的数据</button></div>
    </template><script>
    export default {name: "Child1",props: ['msg'],methods: {changeParentMsg() {this.$emit('update:msg', "我爱你,高圆圆")}}
    }
    </script>
    

    这里使用了 自定义事件update:msg 替换了 自定义事件changeMsg

  3. 此时让组件使用 .sync 修饰符尝试

    <Child1 :msg.sync="string"></Child1>

    发现已经可以实现父子组件见的数据同步了

结论:

使用 sync 条件: 必须实现 1. :xxx 2. @update:xxx

注意:

这里的第二个条件的自定义事件必须是 update: 开头

在哪见过?

在element ui中的对话框dialog中见过

使用场景(主要是区别v-model)

  • v-model 在封装表单元素的时候实现父子组件数据同步,经常使用v-model
  • .sync 在封装非表单元素的组件时候实现父子组件数据同步,经常使用.sync

6、$attrs与$listeners

通过需求来学习 $attrs 和 $listeners

需求: 自定义带Hover提示的按钮(封装组件的意义是通用)

思考:

对element ui 组件进行二次封装的时候,把element里面的每个属性往外一个一个暴露,很累

尝试:

  • $attrs

    $attrs 可以接收到绑定在组件上的所有属性,除去props接收过的属性、style、class类名

在使用的时候使用 v-bind=“$attrs” 直接将这个对象绑定到组件上即可

注意

v-bind不能写成 :

  • $listeners

    $listeners 可以接收到所有绑定在组件上的事件

    在使用的时候使用 v-on=“$listeners” 直接将这个对象绑定到组件上即可

    注意

    v-on 不能简写成 @

<HintButtontype="primary"icon="el-icon-edit"content="编辑"@click="clickHandler"
></HintButton>
<el-tooltipeffect="dark":content="content"placement="top"
><!-- 这里的v-bind和v-on不能简写 --><el-button v-bind="$attrs" v-on="$listeners"></el-button>
</el-tooltip>
export default {name:"HintButton",props: ['content']
}

7、$ref-$children-$parent

$refs 可以获取到组件实例,拿到组件实例可以拿到组件中的数据

<Son ref="son" />this.$refs.son.money -= 100

$children 当前组件的所有子组件实例,得到的是一个数组

this.$children.forEach(child => {child.money -= 100})

$parent 获取当前组件的父组件实例

this.$parent.money += 50

结论: 只要可以获取到当前的组件实例,就可以获取到数据,修改数据
$refs、$children、$parent 目的就是为了拿到组件实例

注意: 这里不推荐使用 $parent, 为什么?

因为父组件不确定是谁

8、mixin

mixin 是混入

混入的概念就是将一段代码混入到另一段代码中
场景:
当组件中有公共的代码需要提出来的时候,可以使用mixin
当多个组件有相同的代码时使用mixin

步骤:

  1. 创建一个 mixin.js 文件,暴露一个对象,这个对象就是vue的配置对象(vue组件中能配置什么,这里就能配置什么,除了el)

    export default {data() {return {msg: '我爱你',}},...... // vue能配置的,这里都可以配置
    }
    
  2. 使用:

    import abc from './mixin'
    export default {mixins: [abc], // 混入的配置项
    }
    

结论:

在mixin中配置的内容(内容包括但不限于数据,方法等),都可以混入到组件当中

如果在组件中有重复的内容,会发生覆盖,组件中的内容会覆盖混入的内容

注意:

钩子函数不会被覆盖,先执行mixin中的钩子,再执行组件中的钩子

9、Provide、Inject

Provide、Inject 用户祖先组件和后代组件之间进行通信

provide 理解成祖先组件的广播(广播数据)

inject 理解成用后代组件来收听广播的(接收数据)

写法:

  • 祖先组件

    data() {return {content1: "jack",content2: {name: "tom",},};
    },
    provide() {return {content1: this.content1,content2: this.content2,changeContent1: this.changeContent1,changeContent2: this.changeContent2}
    },
    methods: {changeContent1() {this.content1 = 'jerry';},changeContent2() {// this.content2 = { // 修改后代无法接收到改变//     name: 'tony'// }this.content2.name = 'tony'; // 修改后代可以接收到改变}
    },
    
  • 后代组件

    data() {return {}
    },
    inject: ['content1', 'content2', 'changeContent1', 'changeContent2']
    

结论注意:

广播只广播一次,当数据发生改变的时候,后代组件当中接收不到更新的信息(包含基本数据类型和引用数据类型的地址)

注意: 在使用引用数据类型的时候,对象中的属性发生改变,后代组件是可以接收到的(不推荐使用,违背了单项数据流)

后代接收的数据:
   基本数据类型不能改
   引用数据类型地址不能改,属性能改
结论:
  当祖先的数据发生改变的时候,后代是接收不到的,也就是说,
  使用provide给后代传参只有在初始化的时候,传递一次
注意:
  对象传递给后代,不能改,能改对象中的属性,
  不能改验证了数据只传递一次(对象传递的是地址)

10、vuex

基本使用

使用步骤:

  1. 安装

    npm i vuex@3 -S

  2. 引入

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
  3. 暴露

    export default new Vuex.Store({ state, mutations, actions, getters, modules })
    
  4. 创建vm的时候,关联store

    import store from '@/store'
    new Vue({render: h => h(App),store
    }).$mount('#app')
    

使用场景:

多个组件依赖同一数据的时候使用 vuex, 例如说: 一般情况下用户信息会放在store中

进阶使用

模块化

配置 modules

只要开启模块化,state中的数据开启了命名模块化
而mutations、actions、getters和之前的使用方式一样没有开启模块化,使用函数命名名称时不能和普通的模式名称相同,否则会冲突

  • state

    普通写法   $store.state.test.count辅助函数 - 往computed中映射
    ...mapState({count: state => state.test.count
    })
    
  • actions

    普通写法   $store.dispath('increment')辅助函数 - 往methods中映射
    ...mapActions(['increment']) 
    
  • mutaions

    普通写法   $store.commit('SETINCREMENT')辅助函数 - 往methods中映射
    ...mapMutaions(['increment']) 
    
  • getters

    普通写法   $store.getters.dblCount辅助函数 - 往compouted中映射
    ...mapGetters(['dblCount'])
    

命名空间

在模块化的每个store当中配置 namespaced: true , 和state、mutations、actions、getters同级

变化:state、mutations、actions、getters都多加了一层命名,分离开来

  • state

    ...mapState('test',['count']),
    
  • actions

    普通写法    $store.dispatch('test/increment')辅助函数
    ...mapActions('test', ['increment'])
    
  • mutaions

    普通写法   $store.commit(test',['SETINCREMENT]')辅助函数 - 往methods中映射
    ...mapMutaions(['test',['increment']) 
    
  • getters

    普通写法    $store.getters['test/dblCount']辅助函数
    ...mapGetters('test', ['dblCount'])
    
<h4>组件使用普通模式</h4><div>count:{{ $store.state.count }}</div>
<div>count:{{ count }}</div><br><div>tenflod:{{ $store.getters.tenflod }}</div><br>
<div>tenflod:{{ tenflod }}</div><br><button @click="$store.dispatch('addCount')">dispatch修改count</button>
<button @click="addCount">dispatch修改count</button><br><button @click="$store.commit('ADD')">Mutations修改count</button>
<button @click="ADD">Mutations修改count</button>computed:{...mapState(['count']),...mapGetters(['tenflod']),},methods: {...mapActions(['addCount']),...mapMutations(['ADD'])}
<h4>组件使用模块化</h4>
<div>count:{{ $store.state.home.count }}</div>
<div>count:{{ count }}</div><br><div>tenflod:{{ $store.getters.homeTenflod }}</div><br>
<div>tenflod:{{ homeTenflod }}</div><br><button @click="$store.dispatch('homeAddCount')">dispatch修改count</button>
<button @click="homeAddCount">dispatch修改count</button><br><button @click="$store.commit('HOMEADD')">Mutations修改count</button>
<button @click="HOMEADD">Mutations修改count</button>computed:{...mapState({count:state => state.home.count}),...mapGetters(['homeTenflod']),},methods: {...mapActions(['homeAddCount']),...mapMutations(['HOMEADD'])}
<h4>组件使用模块化命名空间</h4>
<div>count:{{ $store.state.search.count }}</div>
<div>count:{{ count }}</div><br><div>tenflod:{{ $store.getters['search/tenflod'] }}</div><br>
<div>tenflod:{{ tenflod }}</div><br><button @click="$store.dispatch('search/addCount')">dispatch修改count</button>
<button @click="addCount">dispatch修改count</button><br><button @click="$store.commit('search/ADD')">Mutations修改count</button>
<button @click="ADD">Mutations修改count</button>computed:{...mapState('search',['count']),...mapGetters('search',['tenflod']),},methods: {...mapActions('search',['addCount']),...mapMutations('search',['ADD'])}

11、slot - 插槽

介绍

  • 什么是插槽?

    在模板中开一个槽,这个槽用来接收html内容

    插槽也是父子组件间的一种通信方式,用来传递html、css

    之前在组件标签之间写的任何内容都没啥用,当使用插槽的时候,组件标签之间的的内容会被传递到子组件

  • 为甚要有插槽?

    当需要父组件决定子组件中的某一块内容的时候,就可以使用插槽

  • 插槽怎么玩?

    插槽总共分为三类,普通插槽、具名插槽、作用域插槽

分类

普通插槽

在子组件中写一个slot标签,这个标签就是开的槽

slot标签中如果有内容,默认渲染.如果父组件传html、css了,那么显示父组件传过来的内容

子组件

<slot></slot>

父组件

<Child1><strong class="cont">那个谁塌房了</strong>
</Child1>

注意: 以上是简写,全写如下

<Child1><template v-slot:default><strong class="cont">那个谁塌房了</strong></template>
</Child1>

具名插槽

具有名称的插槽

当子组件中有多个 slot ,默认插槽的内容会填充给每一个 slot ,此时重复了

我们期望的是每一个 slot ,传进去的内容是不一样的,此时给插槽起个名字

子组件

<slot name="qwer"></slot>

父组件

<template v-slot:qwer>内娱完了
</template>

这里相当于给子组件开的槽,slot起了个名字叫 qwer

父组件在给这个槽去传递html、css的时候,需要使用 v-slot: 后面跟给这个插槽起的名字

v-slot:qwer 中v-slot指令是可以缩写的 —> #qwer

这里的 v-slot: 简写成了#

作用域插槽 - 数据

插槽还是原来的插槽,但是涉及到了数据,子组件可以把自己的数据传给父组件的模板中

注意: 这个模板是即将放到当前插槽的模板

子组件

<slot name="user" :users="userinfo" :intro="intro"></slot>

注意: slot标签上,绑定的属性会形成一个对象,而这个对象会在父组件中使用

父组件

<template #user="abc"><div>{{ abc }}</div><h5>姓名: {{ abc.users.name }}</h5><h5>年龄: {{ abc.users.age }}</h5><h5>介绍: {{ abc.intro }}</h5>
</template>全写如下
<template v-slot:user="abc">......
</template>

父组件中 v-slot:user=“abc” 这里的abc就是子组件绑定在 slot 标签上传递过来的数据

注意:

当作用域插槽单独使用的时候,父组件模板中接收的数据的地方写法有以下两种

#default="{ users, instro }"v-slot="{ users, instro }"

12、pubsub

使用步骤:

  1. 安装

    npm i pubsub-js

  2. 接收数据,接收数据的组件,绑定事件(订阅消息),留下回调,接收参数

    Pubsub.subscribe('changeMessage', this.changeMessage)

  3. 传递出局,传递数据的组件,触发事件(发布消息),传递参数
    Pubsub.publish('changeMessage')

  4. 取消订阅
    PubSub.unsubscribe('changeMessage');


http://www.ppmy.cn/news/1021676.html

相关文章

golang专栏

GOLANG专栏订阅会员 Golang基础教程 Golang基础教程 Golang练手算法 Golang练手算法 Golang设计模式 Golang设计模式 Golang数据结构和算法 Golang数据结构和算法 Golang并发编程 Golang并发编程 ORM框架Gorm Golang ORM框架gorm Golang源码分析 Golang源码分析 MySQL教…

在编程下仰望

编程的尽头是数学 数学的尽头是物理 物理的尽头是哲学 哲学的尽头是耶稣 路漫漫其修远兮&#xff0c; 各位码农同仁 好自珍重

unity 修改默认脚本

using System.Collections; using System.Collections.Generic; using UnityEngine; //***************************************** //创建人&#xff1a; xxxx //功能说明&#xff1a; //***************************************** #ROOTNAMESPACEBEGIN# public class #SCRI…

机器学习模型是记忆还是泛化?论文摘要

主要内容摘要 文章探讨了机器学习模型在训练过程中是如何从记忆训练数据转变为正确泛化未见输入的现象。这一现象被称为“grokking”&#xff0c;自 2021 年研究人员在一系列小型模型上的发现后引起了广泛关注。文章通过观察小型模型的训练动态&#xff0c;揭示了这一现象的机…

【云原生】K8S集群

目录 一、调度约束1.1 POT的创建过程1.1调度过程 二、指定节点调度2.1 通过标签选择节点 三、亲和性3.1requiredDuringSchedulingIgnoredDuringExecution&#xff1a;硬策略3.1 preferredDuringSchedulingIgnoredDuringExecution&#xff1a;软策略3.3Pod亲和性与反亲和性3.4使…

SpringCloud源码探析(九)- Sentinel概念及使用

1.概述 在微服务的依赖调用中&#xff0c;若被调用方出现故障&#xff0c;出于自我保护的目的&#xff0c;调用方会主动停止调用&#xff0c;并根据业务需要进行对应处理&#xff0c;这种方式叫做熔断&#xff0c;是微服务的一种保护方式。为了保证服务的高可用性&#xff0c;…

Oracle笔记--dblink

概述 1、database link是定义一个数据库到另一个数据库的路径的对象&#xff0c;database link允许你查询远程表及执行远程程序。在任何分布式环境里&#xff0c;database都是必要的。另外要注意的是database link是单向的连接。 2、在创建database link的时候&#xff0c;Ora…

什么是MCU芯片?分类有哪些?与MPU、SoC的区别

1. MCU芯片 MCU&#xff0c;全称为微控制单元&#xff0c;可以看作是CPU频率和规格的缩减。它整合了计数器、内存、USB和A/D转换等功能&#xff0c;形成了一个芯片级的计算机。MCU的重要性仅次于CPU&#xff0c;广泛应用于各种应用场景&#xff0c;如校园卡、身份证、家用电器…