vue 组件的数据通信方式很多,本篇着重讲$parent/$children
,神助是$broadcast/$dispatch
。
$parent/$children
的常用场景:封装嵌套组件时,直接使用长辈或者子孙组件的方法,该方法并不改变数据,常常结合$broadcast/$dispatch
使用。
什么是$parent/$children
先问一句,div
标签,其父元素和子元素是谁?
这其实没有答案。
父元素和子元素是谁,取决于运行时div
的位置。
试着说说,以下div
的父元素和子元素。
<body>
<div id="div1">
<main>
<div id="div2">
<h1>一级标题<h1>
<p>段落<p>
</div>
<main>
</div>
</body>
div1
:
- 父元素是,
body
- 子元素是,
main
。注意是子元素是指直接后代。
div2
:
- 父元素是,
main
- 子元素是,
h1和p
。注意是子元素是指直接后代。
总结下:
- 元素本身没有父元素和子元素,而是在运行时,每个元素实例的位置,决定其父元素和子元素
- 每个元素实例有且只有一个父元素(顶级元素实例没有哈)
- 但每个元素实例可能没有子元素,可能有一个,也可能有多个,所以一般
children
是数组,没有的时候是空数组 - js 运行时,若添加或者删除元素实例,那些发生位置变化的元素实例们,父元素和子元素也会发生变化。
正文来了!!!组件也是一样滴!!!
因为本来组件就是模仿元素的嘛!!!
把上面的元素换成组件即可!
- 组件本身没有父组件和子组件,而是在运行时,每个组件实例的位置,决定其父组件和子组件
- 每个组件实例有且只有一个父组件(顶级组件实例没有哈)
- 但每个组件实例可能没有子组件,可能有一个,也可能有多个,所以一般
children
是数组,没有的时候是空数组 - js 运行时,若添加或者删除组件实例,那些发生位置变化的组件实例们,父组件和子组件也会发生变化。
其实属性和事件这种机制,也可以类比元素理解,当然这是后话。
举例说明$parent/$children
写一个页面组件,里面放些组件,打印下$parent/$children
<template lang="pug">
//- 页面组件
div
list-item
list-item
</template>
<script>
import ListItem from "@/components/ListItem";
export default {
name: "List",
components: { ListItem },
mounted() {
console.log("页面的$parent", this.$parent);
console.log("页面的$children", this.$children);
}
};
</script>
其实还能看到,渲染的时候先子组件的mounted
,然后再是自己的mounted
.
要是想在子组件里获取父组件的元素之类的,必须使用nextTick
。
$dispatch
在使用element-ui
的时候,有个el-form
,大约是这么用的:
<template lang="pug">
el-form
el-form-item
el-input
</template>
假设el-input
想要执行el-form
上的方法,就会这样this.$parent.$parent.methodXx()
,更多层级可能更复杂,于是$dispatch
就诞生了。
// main.js
// 向上某个组件,派发事件
Vue.prototype.$dispatch = function(eventName, componentName, ...args) {
let parent = this.$parent;
while (parent) {
// 只有是特定组件,才会触发事件。而不会一直往上,一直触发
const isSpecialComponent = parent.$options.name === componentName;
if (isSpecialComponent) {
// 触发了,就终止循环
parent.$emit(eventName, ...args);
return;
}
parent = parent.$parent;
}
};
这样在el-input
里想要触发el-form
里的方法,this.$dispatch('changeSort','el-form',{isAsc:true})
$broadcast
同理,假设el-form
想要执行el-input
上的方法,就会这样this.$children[0].$children[0].methodXx()
,更多层级可能更复杂,于是$broadcast
就诞生了,使用的时候this.$broadcast('changeValue','el-input','hello')
。
注意,$children
是数组,所以当只有一个子组件时,使用[0]获取。当有多个子组件时,它并不保证顺序,也不是响应式的。
// 向下通知某个组件,触发事件
Vue.prototype.$broadcast = function(eventName, componentName, ...args) {
// 这里children是所有子组件,是子组件不是后代组件哈
let children = this.$children;
broadcast(children);
// 这里注意,抽离新的方法递归,而不是递归$broadcast
function broadcast(children) {
for (let i = 0; i < children.length; i++) {
let child = children[i];
const isSpecialComponent = child.$options.name === componentName;
if (isSpecialComponent) {
// 触发了,就终止循环
child.$emit(eventName, ...args);
return;
}
// 没触发的话,就看下有没有子组件,接着递归
child.$children.length &&
child.$broadcast(eventName, componentName, ...args);
}
}
};
更新
感谢评论区,以上可以作为思路拓展,但官方文档不建议使用dispatch,而是倾向于用eventBus
的模式。