-
props/emit ——父子组件传值
-
消息订阅与发布(PubSub)
-
vuex
-
bus 总线
-
provide/inject
-
$parent / $children 与 ref
- $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>