12.组件化开发2-非父子组件之间通信-祖先和后代之间的通信
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了12.组件化开发2-非父子组件之间通信-祖先和后代之间的通信相关的知识,希望对你有一定的参考价值。
参考技术A 在开发中,我们构建了组件树之后,除了 父子组件之间的通信 之外,还会有 非父子组件之间的通信 。非父子组件的通信又可以分为 祖先组件和后代组件之间的通信 、 兄弟组件之间的通信 、 没有任何关系组件之间的通信
这里我们主要讲 祖先组件和后代组件之间的通信
祖先组件通过 provide 向后代组件传递数据
后代组件通过 inject 接收祖先组件传递给后代组件的数据
对象写法:祖先组件只能传给子孙一些固定的数据,不能把data里的数据传给子孙组件
App.vue
App.vue的子组件 Home.vue
App.vue的孙子组件 HomeContent.vue
如果想把祖先组件data中的数据传递给后代组件,需要把provide写成函数形式,此函数返回一个对象
App.vue
App.vue的子组件 Home.vue
App.vue的孙子组件 HomeContent.vue
执行npm run serve运行项目,并在浏览器打开
如果希望 祖先组件 传递给 后代组件 的 数据 ,如果祖先元素中 数据发生改变 ,后代组件接收到的数据也 响应式 发生改变,需要借助vue3提供的 computed Api
App.vue
App.vue的子组件 Home.vue
App.vue的孙子组件 HomeContent.vue
执行npm run serve运行项目,并在浏览器打开
修改 祖先元素中message的值 ,后代组件 接收到的message的值 也会 响应式改变
App.vue
App.vue的孙子组件HomeContent.vue
通过使用 mitt模块 创建的事件总线 emitter 实现
后代组件通过 emitter.emit 发射事件,可以携带参数
祖先组件通过 emitter.on 监听事件,在回调中接收参数
例:想要实现点击HomeContent中的修改message按钮,修改App.vue中的message的值
App.vue的子组件 Home.vue
执行npm run serve,效果如下图,点击了修改message按钮,App.vue中message被改变
在某些情况下我们可能希望取消掉之前注册的函数监听:
003.(高级)组件通信
01. 祖先与后代
-
通过
provide
和inject
可以进行跨级组件通信只能祖先给后代传值
这个注入的值,主要用于读取,不建议更改
确实需要更改,可以从祖先传一个函数,在祖先内进行更改
// 在祖先元素提供 provide(){ return { foo: '123123', app: this // 甚至可以直接返回这个祖先实例 } } // 后代元素 export default { // 在后代元素注入祖先元素提供的值 inject: ['foo'] }
02. 派发与广播
-
通过
$on
和$emit
定义两个自定义事件进行父子组件间的通信$emit
会在当前组件上触发事件,然后在父组件上用v-on
监听这个事件也可以通过
$on
在当前组件自己监听自己 -
通过向上/向下递归遍历,寻找到指定
name
的组件- 每个组件都应设置一个组件名
name
,且组件名应该是唯一的
-
在子组件可以调用
dispatch
方法- 用于向上级查找指定的组件,并触发自定义事件和传递数据
- 上级组件需要用
$on
监听这个事件
-
在父组件可以调用
broadcast
方法- 用于向下级查找指定的组件,并触发自定义事件和传递数据
- 下级组件需要用
$on
监听这个事件
// componentName--组件名,eventName--事件名,params--传递参数 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])); } }); } function 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)); } }, export default { methods: { dispatch(componentName, eventName, params) { dispatch.call(this, componentName, eventName, params); }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } } };
- 每个组件都应设置一个组件名
03.关于mixin
混入
-
使用
mixin
混入,可以分发Vue组件中的可复用功能- 混入对象可以包含任意组件选项,如生命周期、基础数据、实例方法
- 当组件使用时,混入对象的所有选项都将被【混入】到组件中
// mixin.js文件----- let mixin = { // 写法同组件内的写法一致 data(){return {}}, created(){}, methods:{} } export default mixin // 组件中----- import mixin from './mixin' export default { // 局部混入 mixins: [mixin] } // 全局混入 Vue.mixin({ mixin })
- 同名的钩子函数(
created
、mounted
),将合并为一个数组,且混入对象的钩子将在组件钩子之前调用 - 对象选项(
methods
、components
),将被合并为同一个对象,当组件有冲突时,以组件优先
- 混入不同组件的【混入对象】,他们的方法和参数不共享
-
当我们需要全局去注入一些
methods
、filter
、hooks
时我们就可以使用mixin
来实现 -
注意:一旦使用全局混入,它将影响每一个之后创建的 Vue 实例
-
可以通过混入
mixin
将上面的派发与广播方法混入需要的组件中... export default { methods: { dispatch(componentName, eventName, params) { dispatch.call(this, componentName, eventName, params); }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } } };
04. 组件构造器Extend
-
Vue.extend
的作用,就是基于 Vue 构造器,创建一个【类】,用于扩展实例- 它的参数跟
new Vue
的基本一样 - 但data要跟组件一样,是个函数
- 再配合
$mount
,就可以渲染组件,并且挂载到任意指定的节点上
extend创建的是一个组件构造器,而不是一个具体的组件实例
可以理解为构造了一个和vue组件内部有一样结构的对象
最终还是要通过
Vue.components
注册才可以使用let Profile = Vue.extend({ template: '', data() { return { } } }) // 方式一:创建 Profile 实例,并挂载到一个元素上 new Profile().$mount('#app') // 方式二: new Profile({ el: "#app" }) // 方式三:文档之外手动挂载 let myProfile = new Profile().$mount(); export default { mounted(){ // 因为这里是通过获取节点的方式添加到某个元素内,所以一定要在钩子函数中挂载,确保当前页面的dom节点加载完成 let app = document.getElementById('app') app.appendChild(myProfile.$el) } } // 注册组件 Vue.component('myProfile', Profile) // 或者在局部组件 export default { components: { 'myProfile': Profile } }
- 它的参数跟
05. 工具函数
-
通过定义工具函数递归、遍历,找到指定组件的
name
,并返回匹配的组件实例工具函数实现的思路,与派发与广播的思路基本一致
// assist.js // context--组件实例,一般传入this(即当前vue组件实例),components--要查找的组件实例的名称 // 由一个组件,向上找到最近的指定组件 function findComponentUpward (context, componentName) { let parent = context.$parent; let name = parent.$options.name; while (parent && (!name || [componentName].indexOf(name) < 0)) { parent = parent.$parent; if (parent) name = parent.$options.name; } return parent; } // 由一个组件,向上找到所有的指定组件 function findComponentsUpward (context, componentName) { let parents = []; const parent = context.$parent; if (parent) { if (parent.$options.name === componentName) parents.push(parent); return parents.concat(findComponentsUpward(parent, componentName)); } else { return []; } } // 由一个组件,向下找到最近的指定组件 function findComponentDownward (context, componentName) { const childrens = context.$children; let children = null; if (childrens.length) { for (const child of childrens) { const name = child.$options.name; if (name === componentName) { children = child; break; } else { children = findComponentDownward(child, componentName); if (children) break; } } } return children; } // 由一个组件,向下找到所有指定的组件 function findComponentsDownward (context, componentName) { return context.$children.reduce((components, child) => { if (child.$options.name === componentName) components.push(child); const foundChilds = findComponentsDownward(child, componentName); return components.concat(foundChilds); }, []); } // 由一个组件,找到指定组件的兄弟组件: except表示是否把本身除外 function findBrothersComponents (context, componentName, exceptMe = true) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName; }); let index = res.findIndex(item => item._uid === context._uid); if (exceptMe) res.splice(index, 1); return res; } export { findComponentUpward, findComponentsUpward, findComponentDownward, findComponentsDownward, findBrothersComponents };
以上是关于12.组件化开发2-非父子组件之间通信-祖先和后代之间的通信的主要内容,如果未能解决你的问题,请参考以下文章
vue 2 使用Bus.js进行兄弟(非父子)组件通信 简单案例