ref/$parent/$children
基于当前上下文访问父组件或全部子组件:
ref
:给元素或组件注册引用信息;$parent
/$children
:访问父 / 子实例。
这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:
// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>
<template>
# 使用 ref 引用
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
</script>
provide / inject
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
// parent.vue
export default {
provide: {
name: 'Aresn'
}
}
// child.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // Aresn
}
}
所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 Aresn。
===》 解决方式:
之前实习遇到的一个需求场景,下拉选择框 选择了项目就更新下面的tab;但是tab是很深很深。不仅仅是一层;
当时就用到了 provide 和 inject;
但是 provide提供的属性改变了 inject不会改变,即不会触发自身的修改
vue 路由参数变化,页面不刷新,provide /inject 完美解决方案
// 祖先
<template>
<div class="os_container">
<navbar></navbar>
# isRouterAlive改变了之后 就会让下面的页面重新渲染
# 所以定义一个方法reload 修改isRouterAlive(不是真修改,只是通过这个变量触发组件重新渲染
====》 先让他false 然后在挂载之前又更新回来 this.$nextTick
问题:怎么把这个方法 传入给 更深的子组件?使得子组件可以调用这个reload;
当时调用这个方法的子组件是 放在nav里的一个 下拉框; 所以正好是 隔代关系;
<router-view v-if="isRouterAlive"></router-view>
</div>
</template>
<script>
import navbar from '@/components/navbar'
export default {
name:'mainframe',
provide(){
return {
reload:this.reload
}
},
data () {
return {
isRouterAlive:true,
}
},
components:{
navbar,
},
methods:{
reload(){
this.isRouterAlive = false
//在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM
this.$nextTick(()=>{
this.isRouterAlive = true
})
}
}
}
</script>
// nav-input.vue
<script>
export default {
name: 'newproduct',
inject:['reload'],
method:{
selectChange(){
this.reload()
}
}
</script>
原来这就是传说中的 依赖注入,控制反转
进阶,在根入口文件app.vue上挂载基本不会变的数据,比如用户的信息;给全局访问;
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
provide () {
return {
app: this
}
}
data () {
return {
userInfo: null
}
},
}
</script>
全局数据多了可以拆分为 多个js 然后 mixin进来
$on 与 $emit
<template>
<div>
<button @click="handleEmitEvent">触发自定义事件</button>
</div>
</template>
<script>
export default {
methods: {
handleEmitEvent () {
// 在当前组件上触发自定义事件 test,并传值
this.$emit('test', 'Hello Vue.js')
}
},
mounted () {
// 监听自定义事件 test
this.$on('test', (text) => {
window.alert(text);
});
}
}
</script>
自行实现dispatch / broadcast
功能:
事件触发与监听
===》 找到组件,让组件调用相关方法,并传入参数
要实现的 dispatch 和 broadcast 方法,将具有以下功能:
- 在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过
$on
监听了这个事件; - 相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过
$on
监听了这个事件。
如何实现?
实现这对方法的关键点在于准确地找到组件实例。那在寻找组件实例上,我们的“惯用伎俩”就是通过遍历来匹配组件的 name
选项,在独立组件(库)里,每个组件的 name
值应当是唯一的,name 主要用于递归组件
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
// 组件name,自定义事件,要传递的数据
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
// 向上找
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
使用
<!-- A.vue -->
<template>
<button @click="handleClick">触发事件</button>
</template>
<script>
import Emitter from '../mixins/emitter.js';
export default {
name: 'componentA',
mixins: [ Emitter ],
methods: {
handleClick () {
this.broadcast('componentB', 'on-message', 'Hello Vue.js');
}
}
}
</script>
// B.vue
export default {
name: 'componentB',
created () {
this.$on('on-message', this.showMessage);
},
methods: {
showMessage (text) {
window.alert(text);
}
}
}
实现的缺陷:
- 需要额外传入组件的 name 作为第一个参数;
- 无冒泡机制;
- 第三个参数传递的数据,只能是一个(较多时可以传入一个对象),而 Vue.js 1.x 可以传入多个参数,当然,你对 emitter.js 稍作修改,也能支持传入多个参数,只是一般场景传入一个对象足以。
实现findComponents
可以说是组件通信的终极方案。findComponents 系列方法最终都是返回组件的实例,进而可以读取或调用该组件的数据和方法。
它适用于以下场景:
- 由一个组件,向上找到最近的指定组件;
- 由一个组件,向上找到所有的指定组件;
- 由一个组件,向下找到最近的指定组件;
- 由一个组件,向下找到所有指定的组件;
- 由一个组件,找到指定组件的兄弟组件。
5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。
5 个函数的原理,都是通过递归、遍历,找到指定组件的 name
选项匹配的组件实例并返回。