Vue 查找组件实例
作者:Seiya
时间:2019年08月27日
工具函数
我们可以实现一系列的工具函数,进而读取到或调用该组件的数据和方法。
- 向上找到最近的指定组件 -
findComponentUpward
findComponentUpward
接收两个参数,第一个是当前上下文,比如你要基于哪个组件来向上寻找,一般都是基于当前的组件,也就是传入 this;第二个参数是要找的组件的 name 。
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;
}
- 向上找到所有的指定组件 -
findComponentsUpward
与 findComponentUpward 不同的是,findComponentsUpward 返回的是一个数组,包含了所有找到的组件实例。
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 [];
}
}
- 向下找到最近的指定组件 -
findComponentDownward
context.$children 得到的是当前组件的全部子组件,所以需要遍历一遍,找到需要匹配的组件 name,如果没找到,继续递归找每个 $children 的 $children,直到找到最近的一个为止。
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;
}
- 向下找到所有指定的组件 -
findComponentsDownward
这里巧妙使用 reduce 做累加器,并用递归将找到的组件合并为一个数组并返回,代码量较少,但理解起来稍困难。
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);
}, []);
}
- 找到指定组件的兄弟组件 -
findBrothersComponents
findBrothersComponents 多了一个参数 exceptMe,是否把本身除外,默认是 true。
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;
}
tips
与 dispatch 不同的是,这一系列函数是直接拿到组件的实例,而非通过事件通知组件。
自定义指令
上面介绍的获取组件实例的方式,在某些场景并不适用。比如层级较深时,需要递归获取指定组件的实例,性能较差。而且,无法知道组件的更新情况。这里我们可以通过自定义指令实现更优雅的获取组件的实例。
- 自定义 vue 指令
Vue.directive(directiveName || 'ref', {
bind: function bind(el, binding, vnode) {
binding.value(vnode.componentInstance || el, vnode.key);
},
update: function update(el, binding, vnode, oldVnode) {
if (oldVnode.data && oldVnode.data.directives) {
var oldBinding = oldVnode.data.directives.find(function (directive) {
var name = directive.name;
return name === options.name;
});
if (oldBinding && oldBinding.value !== binding.value) {
oldBinding && oldBinding.value(null, oldVnode.key);
binding.value(vnode.componentInstance || el, vnode.key);
return;
}
}
// Should not have this situation
if (vnode.componentInstance !== oldVnode.componentInstance || vnode.elm !== oldVnode.elm) {
binding.value(vnode.componentInstance || el, vnode.key);
}
},
unbind: function unbind(el, binding, vnode) {
Vue.nextTick(function () {
binding.value(null, vnode.key);
});
}
})
- provide
提供可注入的属性,用于获取到组件实例
provide() {
return {
setChildrenRef: (name, ref) => {
this[name] = ref;
},
getChildrenRef: name => {
return this[name];
},
getRef: () => {
return this;
}
}
}
- inject
在组件中注入需要用到的属性:
inject: {
setChildrenRef: {
default: () => {}
},
getParentRef: {
from: "getRef",
default: () => {}
},
}
- 在组件中使用指令
注入之后,可以和指令一起用于设置一个 ref,然后,通过注入的 getRef 属性拿到实例。参照下面的案例,就能够获取到组件的实例:
<componentA v-ant-ref="c => setChildrenRef('componentA', c)" />
methods: {
getARef() {
console.log(this.getParentRef());
},
}