优雅地跨层级获取vue组件实例

在实际的项目开发中,经常遇到组件一层嵌套一层的场景。当组件一两层还好,可以直接用 $refs.A.$refs.B 形式去获取组件的实例。但是,如果组件嵌套层级超过两层时,使用这种方式就不是那么方便了,尤其是获取兄弟组件节点的实例,更加困难。

vue 2.2版本时,新增了 provide/inject 属性,通过 provide 可以轻松地跨层级获取父组件的实例。

// 父组件
provide() {
  return { root: this };
// 子组件
inject: ['root'];

这就解决了从子级获取父级的问题。

但是父级怎么获取子级呢?

使用遍历递归的方式可以做到。通过给子组件设置一个名称,不断的递归 $children ,直到找到正确名称的组件为止。虽然,使用这种方式可以找到自组件实例,但是总是执行递归操作是一件对性能造成浪费的事情。

下面就介绍一下另一种解决办法。

在根组件中设置缓存变量,在每一个组件初始化或更新的时候将对应的实例或者HTMLElement添加到根组件的缓存中,为了实现每一次初始化和更新都调用,这里使用自定义指令。

核心代码如下

export default {
  install(Vue, options = {}) {
    const directiveName = options.name || "ref";
    Vue.directive(directiveName, {
      bind(el, binding, vnode) {
        binding.value(vnode.componentInstance || el, vnode.key);
      update(el, binding, vnode, oldVnode) {
        if (oldVnode.data && oldVnode.data.directives) {
          const oldBinding = oldVnode.data.directives.find(
            directive => directive.name === directiveName
          if (oldBinding && oldBinding.value !== binding.value) {
            oldBinding && oldBinding.value(null, oldVnode.key);
            binding.value(vnode.componentInstance || el, vnode.key);
            return;
          vnode.componentInstance !== oldVnode.componentInstance ||
          vnode.elm !== oldVnode.elm
          binding.value(vnode.componentInstance || el, vnode.key);
      unbind(el, binding, vnode) {
        binding.value(null, vnode.key);

这也是 vue-ref 的实现代码。

在根组件中通过 provide 向外提供设置实例与获取实例的几个关键方法。

provide() {
  return {
    setChildrenRef: (name, ref) => {
      this[name] = ref;
    getChildrenRef: name => this[name],
    getRef: () => this