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());
  },
}
最后更新时间: 2019-8-27 22:26:23