# props 通信

props 通信是最基础最简单的一种通信方式,使用 props 进行组件间通信既可以用来父向子组件传递数据,也可以把子组件的数据传递给父组件。实际上这种通信方式没有任何的限制,它也能够实现爷孙、兄弟通信,只需要层层传递参数即可。但是若关系层级超过 2 层以上,就不推荐使用 props 通信了,面对这种场景我们可以使用全局事件总线更加方便地传递数据。

props 通信通常都是由父组件给子组件传递数据,父组件传递的参数数据大致可以分为函数和非函数两种。当父组件给子组件传递非函数数据即纯数据时,子组件只能被动接收。而当父组件给子组件传递一个函数时,这时我们可以在子组件中调用这个函数,利用给这个定义在父组件里的函数传递形参,从而达到子组件给父组件传参的目的。

子组件在接收父组件传过来的参数时,也有三种方法接收。每种方法都有不同的特点,可以根据不同需求来选择使用

// 父组件
<template>
<div>
    <Son :money="money" :getmoney="getMoney" /> //父给子传参
</div>
</template>
<javascript>
export default {
    data(){
    	money:1000
    },
    methods:{
    	getMoney(money){
    		this.money +=money;
    	}
    }
}
</javascript>
---------
// 子组件 Son
<template>
	<ul>
        <li>{{money}}</li>
        <li>
            <button @click='getmoney(100)'></button>  //子给父传参
    	</li>
    </ul>
</template>
<javascript>
export default {
    //第一种props接收参数的形式,用数组形式接收数据
    props:['money'],
    props:{
    	getmoney:Fuction //利用getmoney这个函数参数,更新父组件的money
    }
    /* 第二种props接收参数的形式,限定props参数类型
    props:{
    	money:Number
    }
    第三种props接收参数的形式,限定参数类型同时指定默认值
    props:{
    	money:{
    		type:Number,
    		default:1
		}
	}
    */
}
</javascript>

# 路由传参里的 props

在路由条目对象里配置 props 本质上就是将 $route.params 映射到 props 中。使用对象传递 params 参数时,路由条目的对象必须使用 name 属性。

{
	name: "search", // 使用对象传 params 参数必须用 name
	path: "/search/:keyword?", // ? 表示 keyword 参数可传可不传
	component: Search,
	props: true // 默认为 false,true 时会将所有的 params 参数映射到 props
	// props:(route)=>({keyword3:route.params.keyword,keyword4:route.query.keyword2})
},

# 自定义事件

在了解自定义事件之前我们需要明确什么是自定义事件。所谓自定义事件其实就是我们自己定义的事件,它和原生 DOM 事件有很大不同。

原生 DOM 事件的特点是:

  • 系统定义的,数量是固定的。就那些事件,事件名是固定的
  • 由系统(即浏览器)管理、触发
  • 回调函数是我们定义的,系统调用的
  • 回调函数的第一个参数是事件对象,是系统自动传入的

Vue 自定义事件的特点是:

  • 自己定义的,数量是无限个,自己想定义多少定义多少,事件名随意取
  • 由我们自己管理、触发
  • 回调函数是我们定义的,系统调用的
  • 回调函数的参数是我们手动传入的,没传就没有

除了以上特点,判断一个事件是否是自定义事件的绝对准则是:所有绑定在组件标签上的事件都是自定义事件。若我们想将组件标签上的自定义事件转换为同名的原生 DOM 事件则需要在自定义事件后加上 .native ,例如 @click.native 。这样就把一个组件标签上的自定义事件 click 转换为同名的原生 DOM 事件 click

在我们使用 vue 的过程中,绑定原生 DOM 事件监听会有两种情况:一是在 html 标签上绑定事件监听,二是在组件标签上绑定事件监听。当我们在组件标签上绑定原生 DOM 事件时,实际上是将事件绑定在了组件的根标签上,利用事件委托来监听所有子组件发生的事件。

// 父组件
<template>
<div>
    <Son @click.native="alert('呵呵呵')">
</div>
</template>
--------
// 子组件 Son
<template>
<div> // 父组件的原生DOM事件绑定在div上
	<h2>哈哈哈</h2>
    <h3>嘿嘿嘿</h3>
</div>
</template>

同样地我们也可以将自定义事件绑定在 html 标签上,但是这样并没有意义。因为自定义事件要在标签内部使用 $emit 触发,而 html 标签无法进入内部触发 $emit

利用自定义事件的特性,我们可以实现子向父通信传递参数

// 父组件
<template>
<div>
    <Son @click="test1" />
</div>
</template>
<javascript>
export default {
	data(){
    	
    },
    methods:{
    	test1(event){
    		alert(event);
    	}
    }
}
</javascript>
--------
// 子组件
<template>
	<div>
        <button @click="$emit('click','嘿嘿')">嘿嘿</button>
        <button @click="$emit('click','哈哈')">哈哈</button>
    </div>
</template>

# PubSubJS

pubsub.js 方式通信是让需要通信的两个组件分别引入该插件,然后再使用。

import pubSub from 'pubsub-js';
// 发布消息的组件中传递数据,就给数据的组件
pubSub.publish('消息名',数据对象);
// 订阅消息的组件,即接收数据的组件
pubSub.subscribe('消息名',(接收数据的参数)=>{});

# 全局事件总线

全局事件总线本质上就是利用作用域链和自定义事件这两个机制来实现组件间通信。** 它可以用于任意组件之间进行数据通信。** 使用全局事件总线有三个步骤,分别是:

  1. 定义总线
  2. 在需要接收参数的组件里,让总线上绑定自定义事件
  3. 在传出数据的组件里,触发总线上的自定义事件

随便用一个对象去定义总线是无效的,成为事件总线的对象要满足两个条件:

  • 该对象能够被所有的组件实例找到
  • 该对象要能够使用 $emit$on 方法
// main.js 
Vue.prototype.$bus = new Vue() //定义事件总线

// Father.vue
this.$bus.$on('addUser',(data)=>{console.log(data)};) // 给事件总线绑定事件

// Son.vue
this.$bus.$emit('addUser',data) // 触发事件总线

全局事件总线的原理

全局事件总线原理

# 作用域插槽

根据作用域插槽的机制,我们可以实现子组件向父组件中传参。因为在作用域插槽中,数据的结构或样式是根据父组件决定的,而此时我们需要将在子组件中遍历过的数据重新传递给父组件,将改变的数据部分用 slot 包裹。在 slot 标签中将数据传递给父组件。父组件再根据传递过来的数据判断需要返回给子组件的样式结构,在 template 标签中使用 slot-scope=子组件属性对象 获取子组件数据。

// 父组件
<template>
<div>
    <templat slot-scope="scope"> // scope对象 = {value:100,index:0}
        
    </templat>
</div>
</template>

//子组件
<template>
	<slot :value="100" :index="0"></slot> // slot的所有属性都会自动传递给父组件
</template>

# Vuex

利用 Vuex 这个插件我们也可以实现组件间通信。当 Vuex 注册为全局组件时,在任一组件里我们都可以用 this.$store 这个变量来从 Vuex 中取得数据并且能够操作数据。 Vuex 相当于充当了一个传话筒的角色,让我们能够在各个组件里自由通信。

对于以上六种组件间通信方式我以前的文章也有总结,戳这里查看更多

# v-model

通常 v-model 是使用在 html 中的表单标签里用于收集数据的。在 html 标签上的 v-model 本质上是用 :value 单向绑定一个数据,然后再用 input 事件触发改变绑定在 :value 上的值来实现的。

<input type='text' v-model='msg' />
// 等价于
<input type='text' :value='msg' @input='msg = $event.target.value' />

当我们在组件标签上使用 v-model 时,本质上也是先用 :value 单向绑定一个值,然后再用自定义的 input 事件监听(将子组件分发数据保存父组件的属性上)。

<myInput v-model='msg'></myInput>
// 等价于
<myInput :value='msg' @input='msg = $event.target.value'></myInput>
--------------
// myInput内部
<template>
	<div>
        <h2>input包装</h2>
        <input type='text' @input='$emit("input",$event.target.value)' />
    </div>
</template>

若组件标签里没有主动设置去使用 $emit 触发自定义事件则 v-model 并不会生效。

组件标签使用 v-model 本质上还是自定义事件和 props 的组合,它实现了父子组件双向数据同步的问题

# Sync 属性

sync 属性修饰符也是用来实现父子组件双向数据同步的问题和 v-model 实现的效果几乎一样。 v-model 一般用于带表单项的组件而 sync 一般用于不带表单项的组件。使用时它们并没有严格的界限,只是我们约定成俗的地会让带有表单项的组件使用 v-model ,而普通组件则使用 sync 属性修饰符。

// 父组件
<h2>不使用sync修改符</h2>
<Child :money="total" @update:money="total=$event"/> // 事件命名格式一定要为update:传过去的数据名称
// 子组件 Child
<button @click='$emit("update:money",100)'></button>

------
// 父组件
<h2>使用sync修改符</h2>
<Child :money.sync="total"/> // 其实就是不使用sync时的语法糖

# $attrs$lintener

当我们需要封装一些组件时,我们可以利用 $attrs$lintener 达到组件复用最大化。其实 $attrs$lintener 就是一个对象, $attrs 可以接收父组件传递过来的全部属性,而 $lintener 可以接收父组件传递过来的全部事件。

// 父组件
<HintButton title="双击添加用户" type="primary" icon="el-icon-plus" @dblclick.native="add2"/>

// 子组件 HintButton
<el-button v-bind="$attrs" v-on="$listeners"></el-button> // 必须用全写v-bind和v-on
-----
// $attrs和$linstener的结构
$attrs = {
	title:"双击添加用户",
	type="primary",
	icon="el-icon-plus"
}

$linstener = {
	@dblclick.native="add2"
}

当我们需要单独去除某个父组件传递过来的属性值时,可以用 props 去单独接收。用 props 接收了的属性不再会出现在 $attrs 对象中

# $Parent$Children$refs

$children :所有子组件对象的数组,因为它返回的是一个数组所以可以使用数组方法对其遍历,但不能用数组下标去访问某个子对象

$parent :代表父组件对象

$refs :ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 当 v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实实例的数组

  • 给子组件标签使用 ref 标识 <Son ref="son" /> $refs 可以直接操作子组件内部的数据及方法通过 this.$refs.son 可以拿到组件对象本身 如果需要修改 data 数据 可以直接修改 this.$refs.son.msg=XXX

  • 给 html 标签使用 ref <p ref='pp'></p> 拿到的是 html 标签本身的 dom 元素 this.$refs.pp

一般慎用以下方法:

找子组件时 $children 是将子组件对象放入数组中不能通过索引操作因为位置不固定, $children 访问子组件顺序是随机的,所以无法使用下标索引操作

找父组件时 $parent 存在组件共用,此时可能不是一个父组件 会存在多个父组件

父组件当中可以通过 $children 找到所有的子组件去操作子组件的数据(当然可以找孙子组件)

子组件当中可以通过 $parent 找到父组件(当然可以继续找爷爷组件)操作父组件的数据

# Mixin 混入技术

html、js、css 相同时我们会封装组件。单个组件里 js 代码重复我们会封装函数。当不同的组件 js 代码重复 封装混合时,我们就可以使用 minx 混入技术,重用 js 代码。新建一个 myminxi.js 文件 在 js 文件中暴露一个对象 对象内部可以有 data methods computed... 会将 js 文件中暴露出的数据 方法等混入到组件内部。

# 使用

// 在组件内部引入 import myminxi from './myminxi.js'
// 使用 mixins:[mymixin]  例如:
import {mixin} from './mymixin'
export default {
    name: 'Daughter',
    mixins:[mixin],
    data(){
        money:1000
    }
},
-------
// myminx.js
export const mixin = {
  methods: {
    borrowMoney (count) {
      this.money -= count
    },
    gaveMoney (count) {
      this.money -= count
      // 给父组件增加 count
      this.$parent.money += count
    }
  }
}
  • 可以提高代码复用,优化性能
  • 变量来源不明确,不利于阅读
  • 多 mixin 可能会造成命名冲突
  • mixin 的组件可能会出现多对多的关系,复杂度较高