vue的组件通信

113 阅读2分钟
  1. props/emit ——父子组件传值

  2. 消息订阅与发布(PubSub)

  3. vuex

  4. bus 总线

  5. provide/inject

  6. $parent / $children 与 ref

  1. $attrs和$listeners、inheritAttrs——父子、祖孙传值

1:props/emit ——父子组件传值

props:父组件向子组件传值

// 父组件写法
<child :name="name" />

// 子组件写法
<script>
export default{
 props:{
  name: {
   type: String,
	default: ''
  }
}
</script>

emit:子组件向父组件传值

// 子组件写法
this.$emit('from-son', this.sonName)


// 父组件写法
<child @from-son="fromSon" />

// 事件触发
fromSon (data) {
	// data就是子组件传来的值
	console.log(data)
}

不支持兄弟组件,也不支持非子后代。


2: 消息的订阅与发布(PubSubJs库)——任意层级传值

下载安装

npm install --save pubsub-js

应用

import PubSub from 'pubsub-js'

// 发布(发送方):写在想要触发传值的操作里,比如button触发的click函数内
PubSub.publish('everything', this.myData)

// 订阅(接收方):
mounted () {
	PubSub.subscribe('everything', (msg, comment) => {
	  // msg参数不能缺少,代表事件名,comment为接收到的数据
	  console.log(comment)
	})
}

可以实现任意组件的通信,不受限制

3:vuex传值——任意层级传值

将多次使用、多次进行相同运算的变量放置于vuex进行状态管理
一般写入四个文件:
index.js
mutation-typse.js
mutations.js
actions.js

index.js

import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'

Vue.use(Vuex)

// state存储想要全局使用的数据
const state = {
  userInfo: null,
  platformName: []
}

export default new Vuex.Store({
  state,
  actions,
  mutations,
  modules: {}
})

在组件中使用的方式

// 1. 从 vuex 中按需导入 mapState 函数
import { mapState } from 'vuex'



// 2. 将全局数据,映射为当前组件的计算属性,   必须是计算属性内
computed: {
  ...mapState(['platformName'])
}

mutation-types.js

export const SET_USER_INFO = 'SET_USER_INFO'
export const GET_UPPER_PLATFORM = 'GET_UPPER_PLATFORM'

mutations.js

只进行一些赋值操作,直接更新state的值,不能写入异步代码;
谁来触发:被actions的commit('mutation名称')触发

import * as types from './mutation-types'

export default {
  [types.SET_USER_INFO] (state, userInfo) {
    state.userInfo = userInfo
  },
  [types.GET_UPPER_PLATFORM] (state, { data }) {
    state.platformName = data
  }
}

actions.js

可写入异步代码;
谁来执行:通过使用commit()触发mutation的调用,间接更新state的值;
谁来触发:组件中:$store.dispatch('actions名称',data)——data为可能需要的参数

import { getUpperPlatform } from '@/api/face'
import * as types from './mutation-types'
export default {
  async getUpperPlatformS ({ commit }) {
    await getUpperPlatform().then(res => {
      const data = res.data
	  
	  // commit(types.GET_UPPER_PLATFORM, { data })的data想要和mutations.js的形参名称保持一致
      commit(types.GET_UPPER_PLATFORM, { data })
	  
    }).catch(e => {
      console.log(e)
    })
  }
}

组件中使用

import { mapState, mapActions } from 'vuex'

// 映射为当前组件的方法
mothods :{
	...mapActions([
	  'getUpperPlatformS'
	]),
	
	// 或者采用第二种方式
	getUpperPlatformS (){
		this.$store.dispatch('getUpperPlatformS')
	}
}

// 调用该方法
created () {
	this.getUpperPlatformS()
}

// 得到调用getUpperPlatformS后,更新过的platformName,并在computed计算属性中映射为组件内变量
computed: {
	...mapState(['platformName'])
},

// 或者在想要操作的地方直接取值,不用计算属性
this.$store.state.platformName

优点在于如果一个值始终是由调用一个异步函数而得到,并且多个页面得到这个值,
那么可在第一次使用到这个函数的时候调用一次异步函数,并将得到的值存储在vuex的state内,其他页面使用时,直接取值即可;
避免多次请求。


问题:如果调用在A页面中写入,在B页面只做取值操作,那么刷新B页面就会取值时,值为空!保存在vuex的数据丢失。

解决办法

created () {
    // this.getUpperInfo()
    // 在页面加载时读取sessionStorage里的状态信息
    if (sessionStorage.getItem('store')) {
      this.$store.replaceState(Object.assign(
          {},
          this.$store.state, 
          JSON.parse(sessionStorage.getItem('store'))
      ))
      // 加载完之后移除数据项
      sessionStorage.removeItem('store')
    }

    // 在页面刷新时将vuex里的信息保存到sessionStorage里
    window.addEventListener('beforeunload', () => {
      sessionStorage.setItem('store', JSON.stringify(this.$store.state))
    })
    
    this.platformNameList = this.$store.state.platformName
  },

4. bus 总线——父子、祖孙、兄弟传值

概念

bus 实际上是一个发布订阅模式,利用 vue 的自定义事件机制,在触发的地方通过 $emit 向外发布一个事件,在需要的页面,通过 $on 监听事件。

用法

第一步,在 main.js 或者在父组件内 写入bus

import Vue from 'vue';

vue.prototype.bus = new Vue()

第二步,发布事件 updateData,传递参数

this.bus.$emit('updateData', arg1, arg2, ...);

第三步,在想要使用数据的子组件内,监听事件,接收参数并写入逻辑

created () {
    this.bus.$on('updateData', (...args) => {
    	// 增加逻辑
    });
}

第四步,组件销毁时卸载发布的事件

beforeDestroy () {
    this.bus.$off('updateData', this.getData);
}

5. provide/inject——父子、祖孙传值


概念

这两个API需要配合使用,祖先组件中通过 provide 来提供变量,然后在子孙组件中通过 inject 来注入变量。

主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档


用法

数据响应式使用,一般来说有两种办法

  • provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以直接修改祖先组件的实例的属性,但是由于实例上挂载了props、methods等很多不需要的东西,因此不是很灵活轻便;
  • 使用 2.6 版最新的 API VUE.observer 优化响应式 provide (推荐)。

举例,子孙组件 F 想要获得 祖先组件 A 的 color

// 组件 A
<div>
  <button @click="changeColor">改变color</button>
  <childrenB />
  <childrenC />
 </div>


data() {
	return {
  	color: "blue"
  }
}

provide() {
	return {
  	theme: { color: this.color }  // 并非响应式,A 改变,F 并不会改变
  }
}

provide() {
	return {
  	theme: this  // 方法一: 提供祖先组件实例
  }
}
methods:{
  changeColor(color) {
    if(color) {
    	this.color = color
    } else {
    	this.color = this.color === "blue" ? "red" : "blue"
    }
  }
}


// 方法二: Vue.observable
provide() {
	this.theme = Vue.observable({
  	color: "blue"
  })
  return { theme: this.theme }
},
methods:{
  changeColor(color) {
    if(color) {
    	this.theme.color = color
    } else {
    	this.theme.color = this.theme.color === "blue" ? "red" : "blue"
    }
  }
}

子孙组件:F组件

<template functional>
  <div class="border2">
    <h3 :style="{ color: injections.theme.color }">F 组件</h3>
  </div>
</template>
<script>
export default {
  inject: {
    theme: {
      //函数式组件取值不一样
      default: () => ({})
    }
  }
};
</script>

6. $parent / $children与 ref——父子传值

该方法都是直接得到组件实例,使用后可直接调用组件的 methods 或者访问 data,无法跨级或兄弟间通信。

  • ref:如果用在子组件上,指向组件实例,可访问 data、methods
  • $parent / $children:访问父子实例,可访问 data、methods
// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
      this.$children[0].consoleHA()   // 打印哈哈哈
    }
  }
}
// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
      
      console.log(this.$parent.title) // 即可得到title的值
      this.$parent.sayHello();
    },
      methods:{
      	consoleHA() {
        	console.log('哈哈哈')
        }
      }
  }
</script>

7. $attrs和$listeners、inheritAttrs——父子、祖孙传值

$attrs和$listeners、inheritAttrs 三者配合可进行父子传值、祖孙传值。

这种方式的传值可读性不是很好。但其对于组件层级嵌套比较深,使用props会很繁琐,或者项目比较小,不太适合使用 Vuex 的时候,可以考虑用它。


inheritAttrs

Boolean 类型,默认值为 true。定义

默认情况下,父作用域的不被认作 props 的 atrribute 绑定,将会 “回退” 且作为普通的 HTML attribute 应用在子组件的根元素上。

设置值为 false,默认行为就会被去掉,即不会作为普通的 attribute 应用到子组件根元素。


先来个栗子👏

父组件如下:

<child class="red"  style="height:80px" :hello="form.hello"/>

子组件:

// 子组件内,虽然父组件传值hello,但是子组件未用 props 属性接收
<template>
  <h-page-container style="height:100%;padding:30px">
  </h-page-container>
</template>

export default {
  name: 'child',
  inheritAttrs: true
}

值为 true 时,编译之后,浏览器

值为 false 时,编译之后,浏览器


但是,通过实例属性 $attrs ,可以将这些特性生效,且可以通过v-bind 绑定到子组件的非根元素上。

改写子组件 template

// 子组件内,虽然父组件传值hello,但是子组件未用 props 属性接收
<template>
  <h-page-container style="height:100%;padding:30px" v-bind="$attrs">
  </h-page-container>
</template>

export default {
  name: 'child',
  inheritAttrs: false
}

那么,此时即使 inheritAttrs: false 得到的结果也和 true 是一样的,均不影响 class style 的绑定。


$attrs

上面讲到,inheritAttrs 过滤掉 props 接收的值,拿到未被接受的值,即 非prop属性

如果,子组件不想继承父组件传入的 非 prop 属性,可设置 inheritAttrs 为 false 禁用继承,然后通过v-bind="$attrs" 将父组件传入的 非prop属性 写在希望继承该属性的标签上,比如孙子组件。

$listener

包含了父作用域中的(不含 .native 修饰器的) v-on 事件监听器。它可以通过v-on="$listeners"传入内部组件。


栗子👏

父组件如下:

<child class="red"  style="height:80px" :hello="form.hello"/>

methods: {
	handleEmit1 () {
    console.log('孙子组件触发我了!')
  },
  handleEmit2 () {
    console.log('孙子组件又触发我了!')
  }
}

子组件:

// 子组件内,虽然父组件传值hello,但是子组件未用 props 属性接收
<template>
  <h-page-container style="height:100%;padding:30px">
    <grandSon v-bind="$attrs" v-on="$listeners"/>  // 孙子组件
  </h-page-container>
</template>

export default {
  name: 'child',
  inheritAttrs: false,
	mounted () {
    console.log(this.$attrs, this.$listeners)
  },
}

孙子组件

<template>
  <div>
    {{ '孙子组件 hello' + $attrs.hello}}
  </div>
</template>

<script>
export default {
  name: 'grandSon',
  mounted () {
    this.$emit('emit1', '')
  }
}
</script>