在理解VUE组件传值深入之前,我们必须理解基础的VUE传值的方式:这里有一篇文章介绍VUE传值的方式
现在VUE的传值的主要方式有:
- props down 与 event up传值(props下发event上传)
- provide 和 inject方式
- $attrs与listeners
- slot内容分发,也可以传值
- eventBus第三方bus的传值方式
- vuex全局状态实现传值
- $ref传递信息
- params与query传值方式
一 prop与event传值
由于父子组件传值是最基础的,所以我们这里不表,现在来看一些其他的传值方式
1.1 父子组件的异步数据传递: props与event实现异步数据传递方式一
父组件
// asyncData为异步获取的数据,想传递给子组件使用
<template>
<div>
父组件
<child :child-data="asyncData" v-if="flag"></child>
</div>
</template>
<script>
import child from './child'
export default {
data: () => ({
asyncData: '',
flag: false
}),
components: {
child
},
created () {
//发送请求获取数据(伪代码)
let async_data = sendAjax()
},
mounted () {
// setTimeout模拟异步数据
setTimeout(() => {
this.asyncData = 'async_data'
this.flag = true
console.log('parent finish')
}, 2000)
}
}
</script>
子组件
<template>
<div>
子组件
<!--不报错-->
<p>{{childObject.items[0]}}</p>
</div>
</template>
<script>
export default {
props: ['childObject'],
data: () => ({
}),
created () {
console.log(this.childObject)// Object {items: [1,2,3]}
},
methods: {
}
}
</script>
上面的代码表示,在created()钩子函数里面发送请求,然后在mounted()钩子里面去更新props数据: this.asyncData = ‘async_data’.那么有几个问题:
1.为什么要这样做?
因为发送请求是异步的,VUE也不知道它多久结束,所以将这个发请求的函数丢进任务队列里,然后去执行页面上的同步程序,当异步函数执行完时才去执行props数据的更新,因为定时器是2秒此时异步数据早就拿到了.
2. 为什么用v-if呢
因为子组件的渲染也是非常快的,有可能在props数据传递之前完成,也有可能在props数据传递之后完成,所以极有可能报错,此时的this.childObject并不是发送请求获取到的数据,而是一个空值.所以报错.
而使用v-if就代表当数据获取到并且赋值给props的时候,才开始渲染子组件,所以解决了数据传递的问题,以及子组件报错的问题.
1.2 父子组件异步数据传递方式二
利用子组件的监听属性来实时获取父组件传递过来的值.
父页面
<template>
<div>
父组件
<child :child="asyncData"></child>
<button @click="changeData">点我改变数据</button>
</div>
</template>
<script>
import child from './child'
export default {
data: () => ({
asyncData: '',
flag: false
}),
components: {
child
},
methods: {
changeData() {
this.asyncData = "我是父组件的值"
}
}
}
</script>
子组件
<template>
<div>
子组件
<!--不报错-->
<p>{{childData}}</p>
</div>
</template>
<script>
export default {
props: ['child'],
data(){
return {
childData: ""
}
},
created () {
//这里的created钩子只会执行一次,所以做不到传递异步数据的效果
// this.childData = this.child
console.log(this.childData) // ""
},
watch: {
"child": function(newVal,oldVal){
this.childData = this.newVal
}
}
}
</script>
这样就可以实现在父子组件中数据的异步传递,在子组件里利用watch监听传递过来的prop的值,一旦发生变化那么将子组件里的值也发生相应的变化.
1.3 父子组件中props实现双向数据绑定
在实现prop的双向数据绑定,我们需要利用到vue的语法糖.sync
父组件:
<template>
<div>
父组件
<child :child.sync="data"></child>
</div>
</template>
<script>
import child from './child'
export default {
data: () => ({
data: 1,
}),
components: {
child
},
watch: {
data(newVal,oldVal){
console.log(newVal)
}
},
}
</script>
子组件:
<template>
<div>
子组件
<button @click="add"></button>
</div>
</template>
<script>
export default {
props: {
child: {
type: Number
}
},
methods: {
add(){
this.msgData++
this.$emit('update:data',this.msgData)
}
}
}
</script>
实现的prop的操作很简单:
- 在父组件传值的时候加上.sync修饰符
<child :child.sync="data"></child>
- 在子组件中触发语法糖的事件update:data
this.$emit('update:data',this.msgData)
其中的原理是:
1.4 子组件实时监听父组件的prop值
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
所以prop值会实时向下传递,但是我们不应该改变prop值,所以一般的操作是子组件中定义一个值,然后将prop值赋值给他,为了实时监听到prop的变化,我们可以这样做:
data(){
return {
bgData: ''
}
},
props: {
testData: {
type: String,
default: '#fff'
}
},
watch: {
testData: {
immediate: true,
handler(newVal,oldVal) {
this.bgData = newVal
}
}
}
利用watch的侦听属性做到实时监听prop的值的变化并及时赋值给子组件里的值,这种方式是为了实时获取到父组件传递的值并赋值给子组件里的数据
二 provide 和 inject方式
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
需要注意的是: provide 和 inject使用在多层嵌套的组件里,每一个父组件下面的子孙组件都可以获取到父组件的值,如果处于中间层级的组件注入了一个值,后面的组件接收的这个值就无法追溯其根源(不知道来自于前面的哪一个组件注入的值),这会造成代码难以维护
三 $attrs与listeners
3.1 $attrs是什么
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
父组件
<son :testData="testData" @upadteTestData="upadteTestData"></son>
data(){
return {
testData: 1
}
},
methods: {
upadteTestData(val) {
console.log(val)
}
}
子组件:
<div @click="addTestData" v-on="$listeners">
我是子组件
</div>
mounted(){
console.log("我是子组件的----mounted钩子------",this.$attrs) //{testData: 1}
},
methods: {
addTestData() {
console.log(this.$listeners)
this.$listeners.upadteTestData(1000)
}
},
在子组件里我们并没有注册props,所以可以用$attrs来获取 prop 被识别 (且获取) 的特性,
四 eventBus实现异步数据的传递
我们知道公共BUS可以传递数据,但是还有一个重要的特性: eventBUS可以传递异步数据,它就像一座桥梁可以在任何时候(任何一个钩子函数中)开启通道并传递数据
现在我们改写代码:
4.1 eventBus代码:
//从vue模块导入vue构造函数
import Vue from 'vue'
const bus = new Vue({})
//导出模块
export { bus }
4.2 定义传递的方法名和传输内容,点击事件或钩子函数触发eventBus.emit事件
// 引入公共BUS
import { bus } from './common/bus.js'
export default {
// 发送请求
created(){
const url = `site/goods/getgoodsinfo/${paramsGoodsId}`
axios.get(url).then(res => {
//处理异步数据,传值给子组件
// 触发自定义事件responseData 传递数据 res.data
bus.$emit('responseData',res.data)
})
}
}
4.3 子组件接收传递过来的数据
import { bus } from './common/bus.js'
export default {
created() {
settimeout(()=>{
// 自定义事件 responseData
bus.$on('responseData', (val) => {
// 子组件接收到的数据 val 就是 res.data
console.log(val)
})
},1000)
}
}
这样就可以在子组件里面接收到父组件的数据了.
为什么要用定时器呢?
数据不一定在created()钩子执行完接收到,因为父组件发送数据的动作是异步的,可能在父组件的mounted()钩子之后才接收到服务器传递来的数据,然后再下发给子组件,
此时子组件的created()钩子肯定执行完了,所以我们利用settimeout开启异步任务,这样在1秒之后就可以接收到数据了(当然也要看数据请求接口的时间).
如果你不希望在created()钩子里面接收到数据,可以不用定时器,直接当数据从父组件传过来之后才操作.这样也是可以的
五 发布订阅模式
刚才说的eventBUS就是典型的发布订阅者模式,其用法页也非常简单:
- 创建的中间的公共BUS
Vue.prototype.bus = new Vue();
- 发布消息
this.bus.$emit('change', this.selfContent)
- 订阅消息
this.bus.$on('change', (msg) => { //订阅change事件
_this.selfContent = msg;
})
六 通过句柄$ref来实现值的传递
$ref就像一个句柄一样,提供了一个从父组件直接操作子组件的入口,它不仅可以拿到子组件的初始值,prop值,以及原生dom的一些属性,同时还可以直接操作子组件methods里的方法
<test-component ref="testComponent"></test-component>
mounted() {
console.info(this.$refs.testComponent)
},