Vue 组件通信方式总结和组件通讯深入

Vue 组件通信方式总结和组件通讯深入

组件通信方法:

父子通信: 
	props
	$parent / $children (返回数组,需要遍历)
	$attrs/$listeners
	$ref 访问组件实例  (this.$refs.xxx.data)
	provide / inject
	

子向父: 自定义事件  ( 子组件通过$emit触发自定义事件,父使用$on接收)

兄弟通信: Bus;Vuex

跨级通信: Bus;Vuex;provide&inject 、 $attrs & $listeners
第一种: props
书写方式: [todos] {todos:Array} {todos:{type:Arrat,default:[]}}

如果父组件给子组件传递数据:

  • 为函数: 本质其实就是子组件给父组件传递数据
  • 为数据: 本质其实就是父组件给子组件传递数据
适用场景:
  1. 父向子

    父组件通过 v-bind 属性绑定向子组件共享数据,子组件使用 props接收数据。

  2. 子向父 (要求父先给子一个函数)

    // 父组件
     <Child :getData="getData"></Child>
     data() {
       return { get: '' }
     },
     getData(event) {
       this.get = event
     }
    
    // 子组件
    <template>
      <div>
        <button @click="emitEvent">点击获取</button>
        {{ one }}
      </div>
    </template>
    <script>
    export default {
      name: 'Child',
      props: ['getData'],
      data() {
        return {
          one: 'data data data'
        }
      },
      methods: {
        emitEvent() {
          this.getData(this.one)
        }
      }
    }
    </script>
    
    

注意:props的值如果是对象类型的值修改时不会报错,不能使用v-model绑定props的值,因为props的值不可以修改!

第二种: 自定义事件

子组件通过 e m i t , 父 组 件 通 过 emit ,父组件通过 emiton接收

书写方式:
    两种【简单写法|复杂写法】
		<Todos @erha="handler">
		<Todos ref='erha'>
        mounted(){
           this.$refs.erha.$on('xx',callBack)
        }
第三种: 全局事件总线 $bus

该方法通过Vue实例对象作为中央事件总线,用她来触发事件和监听事件,巧妙得实现了任何组件之间的通信。

使用:

// 挂载全局事件总线
Vue.prototype.$bus = this;

适用场景:万能(兄弟、父子、任何组件都可以实现通信,`但通常用于兄弟组件传值`
// 发送方组件
 <span>{{elementValue}}</span>
 <input type="button" value="点击触发" @click="elementByValue">
 <script> 
 export default {
    data () {
      return { elementValue: 4 }
    },
    methods: {
      elementByValue() {
        Bus.$emit('val', this.elementValue)
      }
    }
  }
</script> 


// 接收方
  <input type="button" value="点击触发" @click="getData">
  <span>{{name}}</span>

<script>
  export default {
    data () {return {name: 0 }},
    mounted () {
      // 用$on事件来接收参数
      Bus.$on('val', (data) => {
        this.name = data
      })
    },
    methods: {
      getData: function () {
        this.name++
      }
    }
  }
</script>

第四种:Vuex 全局组件共享

第五种:slot插槽 适用于父子组件通信 (dom结构)

解析 Vue实例对象
$root 根实例
$options 每一个Vue实例都有一个实例对象 options
$children  子组件实例 (是一个数组)
$parent    父组件实例
$listeners 获取到父组件给子组件的传递的自定义事件
 
$emint 子组件抛出的自定义事件
$on 通过on接收自定义事件
$attrs和$props 加起来才是所有子组件的所有自定义属性:

$attrs  能获取到子组件所有未被props接收的属性 (排除了$props、class、style以外的其他属性,极端情况下使用,注意不能直接用模板字符串使用$attrs的数据)
$props  获取到子组件的所有props,父子传参时使用,正常情况下都用prpos传参,因为是响应式的数据也更安全。
$data 拿到组件完整的data对象
$refs  如果是HTML标签,拿到的是dom对象,如果是组件标签,拿到的是组件实例对象(从而操作组件的数据和方法)
$vnode 当前组件的虚拟节点
$router 拿到VueRouter实例
$route  获取组件的路由信息(name、meta、path、query等参数)

注意: App.vue  并不是根实例 ,是`$root根实例`的子组件。
1. event 事件对象
    <button @click="handler">第一个原生btn按钮</button>
    <Event1 @click"handler1"></Event1>
	<Event1 @click.native="handler1"></Event1>
	<Event2 @click="handler2" @xxx="handler2"></Event2>

	<!-- 自定义事件对于原生DOM没有任何意义 因为没有办法触发$emit函数 -->
	<!-- <button @erha="handler3"> 原生的btn</button> -->
	<input type="week" />

methods: {
      //原生DOM事件的回调
      handler(event){
        console.log('原生dom的click事件')
        console.log(event);
      },
      handler1(){
        console.log('66666666');
      },
      handler2(params){
       console.log(params);
      }
    }

当我们点击 第一个原生btn按钮时,控制台会输出 event 对象,但是点击第一个组件时就无法触发,因为组件上的原生dom事件会被默认当做自定义事件,要想在组件标签上触发原生dom事件,需要加入.native修饰符,native这个词就是原生的意思, 他的作用就是 把自定义事件变为 原生DOM事件

2. v-model 深入

我们通常使用 v-model 指令用于收集表单数据 [textradiocheckboxrange] 等等,进行数据的双向绑定和展示,注意v-model 收集checkbox需要用数组收集。

父组件:


	<input type="text" v-model="msg">
    <span>{{msg}}</span>
   
    <!-- 原生DOM当中是有oninput事件:当表单元素发生文本的变化的时候就会立即出发 -->
	<!-- 原生DOM:通过 value与input事件实现 v-model 功能-->
    <input type="text" :value="msg" @input=" msg = $event.target.value"/>
    <span>{{msg}}</span>

    <!-- 并非原生DOM: 在组件中实现父子组件通信和数据同步-->
    <!-- :value 是什么? 是一个props,用于实现父子组件通信
         @input 是什么? 并非原生DOM的 input 事件,而是一个自定义事件 
     -->

    <CustomInput :value="msg" @input="msg = $event"></CustomInput>  
	 <!-- 简化写法,和上一行代码效果一样 -->
    <CustomInput v-model="msg"></CustomInput>

<script type="text/ecmascript-6">
  import CustomInput from './CustomInput.vue'
  export default {
    name: 'ModelTest',
    data() {
      return {
         msg:"我爱塞北的大雪呀"
      }
    },
    components: {
      CustomInput
    }
  }
</script>

CustomInput 子组件:

<template>
  <div style="background: #ccc; height: 50px;">
    <h2>input包装组件----{{value}}</h2>
	 <!-- 这里的 :value 和 @input 是什么?
         :value 动态属性
         @input 给原生dom绑定原生dom事件 -->
    <input :value="value"  @input="$emit('input',$event.target.value)"/>
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    name: 'CustomInput',
    props:['value']
  }
</script>

注意: 在上面代码中input 使用了 :value=“msg”,是用于单向数据绑定,也就是说输入框会默认显示data中msg的值,而在 CustomInput 组件中使用的 :value="msg"是 props

总结:

// v-model:实现原理   :value  @input  还可以实现父子数据同步。
<CustomInput v-model="msg"></CustomInput>
3. 使用属性修饰符 .sync 实现父子组件同步

【案例】: 小明的爸爸有多少钱 ?

父组件:

<template>
  <div>
    小明的爸爸现在有{{ money }}元
    <h2>不使用sync修改符</h2>
     <!-- 以前我们写自定义事件都是写一个名字,这里的 @update:money 中的money就是props中的money 
		  因为我们这里实现逻辑简单,没有写相应的回调函数,
		  这里只需要让money的值更新一下(子组件自定义事件传回来的数据)-->
    <Child :money="money" @update:money="money = $event"></Child>
      
    <h2>使用sync修改符</h2>
     <!-- :money.sync的含义:
		  第一: 父组件给子组件传递props,名为money
		  第二: 给当前子组件绑定了一个自定义事件,而且事件名称即为 @update:money"-->
    <Child :money.sync="money"></Child>
  
  </div>
</template>

<script >
import Child from './Child.vue'
export default {
  name: 'SyncTest',
  data() {
    return {
      money: 10000
    }
  },
  components: {
    Child,
    Child2
  }
}
</script>

子组件 Child:

<template>
  <div style="background: #ccc; height: 50px;">
   <!-- 触发自定义事件,第一个参数是触发自定义事件的名字 update:money,
		第二个参数是子组件需要把父亲还剩多少钱告诉父亲 -->
    <button @click="$emit('update:money',money - 100)">点击花费100元</button>
    爸爸还剩 {{money}} 元
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    name: 'Child',
    props:['money']
  }
</script>
4. a t t r s 与 attrs与 attrslisteners 实现父子组件通信

多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此 Vue2.4 版本提供了另一种方法 $attrs / $listeners

$attrs:组件实例的属性,能获取到子组件所有未被props接收的属性
$listeners:组件实例的属性,可以获取到父亲传递自定义事件(对象形式呈现)

【案例】:基于 element-ui 实现自定义带 Hover 提示的按钮:

父组件:

<template>
  <div>
    <h2>自定义带Hover提示的按钮</h2>
    <!-- 二次封装的HintButton按钮的时候,把人家el-button需要的数据传递过去 -->
    <!-- 注意这里的@click 代表自定义事件而非原生dom的click事件 --> 
     <HintButton type="success" icon="el-icon-plus" title="我是中国人" @click="handler"/>
  </div>
</template>

<script >
import HintButton from './HintButton';
export default {
  name: 'AttrsListenersTest',
  components:{
     HintButton 
  },
  methods: {
    handler() {
       alert('点击按钮时弹出的自定义事件!');
    },
  },
}
</script>

子组件:

<template>
  <div>
     <!-- 可以巧妙的利用 a标签的title属性,实现按钮带有提示功能 -->
     <a :title="title">
         <!-- 下面这行注释的代码写得很呆,如果有很多属性的话一个个接收太麻烦了-->
         <!-- <el-button :type="$attrs.type" :icon="$attrs.icon" v-on="$listeners">添加</el-button>-->
		<!-- 我们可以直接使用`v-bind="$attrs"` 但是不能简写成 :、 v-on也不能简写-->
        <el-button v-bind="$attrs" v-on="$listeners">添加</el-button>
     </a>
  </div>
</template>

<script>
export default {
  name: "",
  props:['title'],
  mounted(){
    // this.$attrs:可以获取到父亲传递的属性 (排除了$props、class、style以外的其他属性)
    // 打印后发现: this.$attrs 是获取不到的 title 的,因为title已经通过 props 接收
    console.log(this.$attrs);
    // $listeners 获取到父组件给子组件的传递的自定义事件(也是组件实例自身的属性)
    console.log(this.$listeners);
  }
};
</script>

<style scoped></style>
5. $ refs 、 c h i l d r e n 、 children、 childrenparent 实现父子组件通信

$refs:父组件访问子组件
	如果在普通的DOM元素上使用,引用指向的是DOM元素;
	如果用在子组件上,引用的是组件实例
	
$children:父组件访问子组件 (如果是多次的$refs操作,我们可以使用$children属性)
$parent:  子组件访问父组件

【案例】:父亲向孩子借钱,动态展示父亲和孩子们的剩余存款

父组件:

<template>
  <div>
    <h2>父亲有存款: {{ money }}</h2>
    <button @click="JieQianFromXM(100)">找小明借钱100</button><br />
    <button @click="JieQianFromXH(150)">找小红借钱150</button><br />
    <button @click="JieQianAll(200)">找所有孩子借钱200</button><br />
    <button @click="SendInfo">我是baba</button>
    <br />
    <!-- 小明 -->
    <Son ref="xm" />
    <br />
    <!-- 小红 -->
    <Daughter ref="xh"/>
  </div>
</template>

<script>
import Son from "./Son";
import Daughter from "./Daughter";

export default {
  name: "ChildrenParentTest",
  data() {
    return {
      money: 1000,
    };
  },

  methods: {
    //找儿子借钱
    JieQianFromXM(money) {
      this.money += money;  // 点击按钮父亲存款每次 +100
      //  $ref 可以获取真实dom节点,也可以获取子组件标签实例对象,从而操作组件的数据和方法
      this.$refs.xm.money -= money;  // 儿子存款每次 -100
    },
    JieQianFromXH(money) {
      //父组件的数据累加150
      this.money += money;
      this.$refs.xh.money -= money;
    },
    JieQianAll(money){
      this.money += 2 * money; // 父亲向所有孩子借钱,得到二倍money
        
 	  // 不使用 $fefs,使用 $children 一样可以获取子组件的实例对象 [返回的是一个数组]
        
      this.$children.forEach(item=>item.money-=money);
      //不建议用枚举获取子组件:因为组件过多时,没办法确定到底是那个子组件
      // this.$children[0].money -=money;

    },
    SendInfo(){
      //在父组件中获取到子组件(数据+方法)
      this.$refs.xm.tinghua();
    }
  },

  components: {
    Son,
    Daughter,
  },
};
</script>

子组件小明 Son.vue :

<template>
  <div style="background: #ccc; height: 50px;">
    <h3>孩子小明: 有存款: {{money}}</h3>
    <button @click="giveMoney(50)">给父亲50元</button>
  </div>
</template>

<script>
export default {
  name: 'Son',
  data () {
    return {
      money: 30000
    }
  },
  methods: {
    tinghua(){
        console.log('我是小明,我听爸爸的话');
    },
    // 儿子给父亲钱的回调函数
    giveMoney(money){
       this.money-=money;  // 儿子 -50
       // 在子组件内部获取父亲的存款再 +50
       this.$parent.money += money;  
    }
  }
}
</script>

子组件小红 Daughter.vue :

<template>
  <div style="background: #ccc; height: 50px;">
    <h3> 孩子小红: 有存款: {{money}}</h3>
    <button @click="giveMoney(666)">给父亲666</button>
  </div>
</template>

<script>
export default {
  name: 'Daughter',
  data () {
    return {
      money: 20000
    }
  },

  methods: {
    giveMoney(money){
       this.money-=money;  
       this.$parent.money += money;  
    }
  }
}
</script>

总结:

$ref : 可以在父组件内部获取子组件—实现父子通信

$children: 可以在父组件内部获取全部的子组件【返回数组】

$parent: 可以在子组件内部获取唯一的父组件【返回组件实例】

6. 通过 provide/inject 轻松实现跨级访问祖先组件的数据

使用方法:provide在父组件中返回要传给下级的数据,inject在需要使用这个数据的子辈组件或者孙辈等下级组件中注入数据。

// 父组件通过provide将自己的数据以对象形式传出去
  provide(){
    return {
      parentValue:"我是父组件的值啊"
    }
  }
 
// 子孙组件接受方式:
// inject:["parentValue"], // 使用一个注入的值作为数据入口:
  inject:{
    // 使用一个默认值使其变成可选项
    parentValue: { // 健名
      from: 'parentValue', // 来源
      default: 'parentValue' // 默认值
    }
  }

注意:provide并不是响应式的,当子组件inject的时候已经丢失了响应式功能。

如实现父组件与子孙组件所绑定的数据动态响应呢?

-------------------父辈组件----------------------
provide(){
    return {
   // keyName: {name:this.name}, // value 是对象才能实现响应式,也就是引用类型
      keyName: this.changeValue // 通过函数的方式也可以[注意,这里是把函数作为value,而不是this.changeValue()]
   // keyName: 'test' value 如果是基本类型,就无法实现响应式
    }
  },
data(){
  return {
	name:'张三'
}
  },
  methods: {
  	changeValue(){
  		this.name = '改变后的名字-李四'
  	}
  }  
  -------------子、孙辈组件-----------------
  inject:['keyName']
  create(){
	console.log(this.keyName) // 改变后的名字-李四
}

当我们以如上的形式书写代码的时候,其实相当于对this.changeValue做了一层浅拷贝,虽然inject的时候已经丢失了响应式功能。但由于仅仅是浅拷贝,所以响应式对象的属性仍旧是响应式的。

依照这个思路,如果我们希望整个数据都是响应式的。那么可以通过一下的方法实现:

-------------------父辈组件----------------------

  provide() {
    return {
      reactiveMsg: () => this.msg
    }
  },


  -------------子、孙辈组件-----------------
{
  inject: ['reactiveMsg'],

  computed: {
    computedProperty() {
      return this.reactiveMsg()
    }
  },
  watch: {
	computedProperty() {
		console.log('数据改变')
	}
  }
}

如上provide提供一个函数。函数内部返回一个响应式的数据。此时整条数据的响应式的状态并不会丢失。

而且这样做有一个好处,即无法直接修改computedProperty的值,因为他是一个计算属性。这样就可以避免数据的混乱。

【实例】:让所有组件共享 App.vue的数据和方法

provide() {
    return {
      app:this
    }
  }

接下来,任何组件只要通过inject:['app'],都可以通过this.app.xxx的形式来访问App.vue的data,computed,method等内容。

使用场景:
  1. 比如在App.vue中我们调用了获取用户信息的接口,其他页面需要使用这个方法。

  2. 刷新vue组件

    ------------------app.vue-----------------------
    
    <template>
      <div id="app">
        <!-- isRouterAlive 保证keep-alive时,不影响全局刷新 -->
        <div v-if="isRouterAlive">
          <keep-alive>
            <router-view v-if="$route.meta.keepAlive">
              <!-- 这里是会被缓存的视图组件,比如列表A页面 -->
            </router-view>
          </keep-alive>
     
          <router-view v-if="!$route.meta.keepAlive">
            <!-- 这里是不被缓存的视图组件,比如详情B页面-->
          </router-view>
        </div>
      </div>
    </template>
     
    <script>
    export default {
      name: 'App',
      components: {
        MergeTipDialog,
        BreakNetTip
      },
      data () {
        return {
          isShow: false,
          isRouterAlive: true
      },
     
    // 父组件中返回要传给下级的数据
      provide () {
        return {
          reload: this.reload
        }
      },
      methods: {
        reload () {
          this.isRouterAlive = false
          this.$nextTick(() => {
            this.isRouterAlive = true
          })
        }
      }
    }
    </script>
    
    ------------------子孙组件-----------------------
    <template>
      <popup-assign
        :id="id"
        @success="successHandle"
      >
        <div class="confirm-d-tit"><span class="gray-small-btn">{{ name }}</span></div>
        <strong>将被分配给</strong>
        <a
          slot="reference"
          class="unite-btn"
        >
          指派
        </a>
      </popup-assign>
    </template>
    <script>
    import PopupAssign from '../PopupAssign'
    export default {
    //引用vue reload方法
      inject: ['reload'],
      components: {
        PopupAssign
      },
    methods: {
        // ...mapActions(['freshList']),
        async successHandle () {
          this.reload()
        }
      }
    }
    </script>
    
solt 插槽

作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父➡子组件通信。

作用域插槽:子组件的数据来源于父组件,子组件决定不了自身结构和外观。

通过props实现父子传值时为什么传入数字也需要使用 v-bind 呢 ?

为了告诉 Vue,传入的是 这是一个 JavaScript 表达式,如果不使用v-bind 将会被解析成一个字符串。 正确写法: <Son :likes="42"/>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OooooYi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值