彻底理解Vue组件间7种方式通信
一、父子组件通信:
1.1、传递静态或动态的 Prop
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
这样,你已经知道了可以像这样给 prop 传入一个静态的值:
<blog-post title="My journey with Vue"></blog-post>
你也知道 prop 可以通过 v-bind
或简写 :
动态赋值,例如:
<!-- 动态赋予一个变量的值 --> <blog-post :title="post.title"></blog-post> <!-- 动态赋予一个复杂表达式的值 --> <blog-post :title="post.title + ' by ' + post.author.name"></blog-post>
举例子:
父页面:
子页面:
展示:
1.2、子父组件emit通信
子页面
<button v-on:click="giveAdvice">Click me for advice </button>
data() { return { possibleAdvice: ['Yes', 'No', 'Maybe'] }; },
methods:{ giveAdvice: function () { console.log('子页面', this.possibleAdvice ) var randomAdviceIndex = Math.floor(Math.random() * this.possibleAdvice.length) console.log('子页面randomAdviceIndex---', randomAdviceIndex)
// 名字give-advice和父元素保持一致 this.$emit('give-advice', this.possibleAdvice[randomAdviceIndex]) } },
父页面
// 名字give-advice和子页面$emit名字保持一致
<a-blog v-on:give-advice="showAdvice" :title="post.title + ' by ' + post.author.name" :post="post"/>
methods: { showAdvice: function (advice) { console.log('-父页面-advice---', advice) } }
结果:
1.3、provide / inject
详细:
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。
inject 选项应该是:
一个字符串数组,或
一个对象,对象的 key 是本地的绑定名,value 是:
在可用的注入内容中搜索用的 key (字符串或 Symbol),或
一个对象,该对象的:
from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
default property 是降级情况下使用的 value
父页面APP.vue
import ABlog from './components/demo/a-blog.vue' name: 'App', provide: { foo: 'I from parents bar' }, components: { ABlog },
子页面a-blog.vue
<b-blog />
import BBlog from './b-blog.vue' ..... name: 'App', components: { BBlog },
孙子页面:b-blog.vue
<div class='b-blog'> <p>孙子页面:{{foo}}</p> </div>
..... name: 'b-blog', inject: ['foo'], created() { console.log('孙子页面inject---', this.foo) // => "bar" },
Vue.observable( object )
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景:
父页面:
<button @click="() => changeColor()">改变color</button>
import ABlog from './components/demo/a-blog.vue' name: 'App', provide() { // 方法二:使用2.6最新API 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"; } } }
儿子页面
<b-blog /> ... import BBlog from './b-blog.vue' ... components: { BBlog },
孙子页面
<p :style="{ color: theme.color }">孙子页面b-blog 组件</p> ... inject: { theme: { //函数式组件取值不一样 default: () => ({}) } },
1.4、$parent / $children与 ref
$parent / $children
父页面:
<button @click="changeVal">点击改变子组件的值</button> .... data() { return { msg: 'hello, I from parents page!' }; }, .... methods: { changeVal(){ console.log('父页面----', this.$children[0]) this.$children[0].message = "hello, " } }
子页面:
<span>改变自组建的的值::{{message}}</span> <p>子页面this.$parent.msg获取父组件的值:{{parentVal}}</p> .... data() { return { possibleAdvice: ['Yes', 'No', 'Maybe'], message:'songxiaotao**' }; }, .... components: { BBlog }, computed: { parentVal(){ return this.$parent.msg; } }, ...
点击按钮前
点击按钮后
ref的用法
父页面:
<a-blog ref="refDom" v-on:give-advice="showAdvice" :title="post.title + ' by ' + post.author.name" :post="post"/>
mounted () { const refDom = this.$refs.refDom // 打印子页面的refDom的数据和方法 console.log('mounted---refDom ---', refDom, refDom.message, refDom.possibleAdvice) refDom.refFn()// refDom.giveAdvice()// },
子页面
1.5、$emit
/$on
假如一个页面AB兄弟组件通信, 可以用此方法
var EVENT=new Vue(); vue.prototype = EVENT this.EVENT.$emit(事件名,数据); this.EVENT.$on(事件名,data => {});
this.EVENT.$on('test', function (msg) { console.log(msg) }) this.EVENT.$emit('test', 'hi') // => "hi"
1.6、attrs/$listeners
2.4.0 新增
-
类型:
{ [key: string]: string }
-
只读
-
详细:
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (
class
和style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和style
除外),并且可以通过v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。 -
vm.$listeners
2.4.0 新增
-
类型:
{ [key: string]: Function | Array<Function> }
-
只读
-
详细:
包含了父作用域中的 (不含
.native
修饰器的)v-on
事件监听器。它可以通过v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
-
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,这就有点大材小用了。所以就有了 $attrs
/ $listeners
,通常配合 inheritAttrs 一起使用。
inheritAttrs:默认值为 true。默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置
inheritAttrs
到false
,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property$attrs
可以让这些 attribute 生效,且可以通过v-bind
显性的绑定到非根元素上。查 看 官 网
感觉还是挺晦涩难懂的,简单的说就是 inheritAttrs:true 继承除props之外的所有属性;inheritAttrs:false 只继承class属性
$attrs
:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。
$listeners
:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
来看看例子加深一下印象
父组件:
<template> <div id="app"> <a-blog :width="width" :height="height" :book="book" :fruit="fruit" @updateFn="handleUpdate" @deleteFn="handleDelete"/> </div> </template>
data() { return { width: 300, height: 200, book: 'java', fruit: 'banana', weight: '70kg' }; }, ... components: { ABlog }, ... methods: { handleUpdate(val){ console.log('父组件已经接收到--update info', val); }, handleDelete(){ console.log('delete info'); } } ...
儿子页面:a-blog.vue
<p> 儿子页面: attrs-- {{ $attrs }} --- {{ $listeners }}</p> <b-blog @addInfo="addInfo" v-bind="$attrs" v-on="$listeners"/> ... import BBlog from './b-blog.vue' props: { height:[String, Number], }, components: { BBlog }, ... created () { console.log('儿子页面-this.$attrs', this.$attrs); console.log('儿子页面-this.$listeners', this.$listeners); // updateInfo: f, delInfo: f }, ... methods:{ addInfo(){ console.log('---addInfo----') } }, }
孙子页面:b-blog
<template> <div class='b-blog'> <!-- <p>孙子页面:{{foo}}</p> --> <!-- <p :style="{ color: theme.color }">孙子页面b-blog 组件</p> --> <p> 孙子页面--- attrs-- {{ $attrs }} --- {{ $listeners }}</p> </div> </template> .... props: { book:[String, Number] }, ... created() { // console.log('孙子页面inject---', this.theme) // => "bar" console.log('孙子页面-this.$attrs', this.$attrs); console.log('孙子页面-this.$listeners', this.$listeners); this.$emit('updateFn', {a:'孙子传过来的的值'}) },
1.7、vuex
-----------------陆续更新中-----