$parent/$children的使用场景 -- vue组件通信系列
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); } } };
更新
感谢评论区,以上可以作为思路拓展,但官方文档不建议使用broadcast/broadcast/broadcast/dispatch,而是倾向于用eventBus
的模式。