5. Find any component instance – findComponents series method

Overview

In the previous section, we have introduced two methods of communication between components: provide / inject and dispatch / broadcast. They have their own usage scenarios and limitations. For example, the former is mostly used for child components to obtain the state of the parent component, and the latter is often used for communication between parent and child components through custom events.

This section will introduce the third component communication method, that is, the findComponents series of methods. It is not built into Vue.js, but needs to be implemented by itself and used in the form of tool functions. It is a series of functions, which can be said to be component communication. ultimate solution. The findComponents series of methods eventually return the instance of the component, and then the data and methods of the component can be read or called.

It is suitable for the following scenarios:

  • From a component, find the nearest specified component upwards;
  • From a component, find all specified components upward;
  • From a component, find the nearest specified component down;
  • From a component, find all specified components down;
  • From a component, find the sibling components of the specified component.

5 different scenarios correspond to 5 different functions, and the implementation principles are similar.

Implementation

The principle of the five functions is to find and return the component instance matching the name option of the specified component through recursion and traversal.

This section and subsequent chapters are based on the project in the previous section, and will not be repeated in the future.

Complete source address: https://github.com/icarusion/vue-component-book

Create a new folder utils under the directory src to place tool functions, and create a new file assist.js, all functions in this section are in this file Each function is provided externally through export (if you don’t know about export, please refer to Extended Reading 1).

Find the nearest specified component up–findComponentUpward

Look at the code first:

// assist.js
// From a component, find the nearest specified component up
function findComponentUpward (context, componentName) {<!-- -->
  let parent = context. $parent;
  let name = parent. $options.name;

  while (parent & amp; & amp; (!name || [componentName].indexOf(name) < 0)) {<!-- -->
    parent = parent. $parent;
    if (parent) name = parent. $options. name;
  }
  return parent;
}
export {<!-- --> findComponentUpward };

findComponentUpward receives two parameters, the first one is the current context, such as which component you want to search upwards, usually based on the current component, that is, pass in this; the second parameter is to The name of the found component.

The findComponentUpward method will continuously overwrite the current parent object in the while statement, by judging whether the name of the component (that is, parent) is consistent with the passed componentName, until the nearest component.

Different from dispatch, findComponentUpward gets the instance of the component directly instead of notifying the component through an event. For example, in the following example, there are component A and component B, A is the parent component of B, and the data and methods in A are obtained and called in B:

<!-- component-a.vue -->
<template>
  <div>
    Component A
    <component-b></component-b>
  </div>
</template>
<script>
  import componentB from './component-b.vue';

  export default {<!-- -->
    name: 'componentA',
    components: {<!-- --> componentB },
    data () {<!-- -->
      return {<!-- -->
        name: 'Aresn'
      }
    },
    methods: {<!-- -->
      sayHello () {<!-- -->
        console.log('Hello, Vue.js');
      }
    }
  }
</script>
<!-- component-b.vue -->
<template>
  <div>
    Component B
  </div>
</template>
<script>
  import {<!-- --> findComponentUpward } from '../utils/assist.js';

  export default {<!-- -->
    name: 'componentB',
    mounted () {<!-- -->
      const comA = findComponentUpward(this, 'componentA');
      
      if (comA) {<!-- -->
        console.log(comA.name); // Aresn
        comA.sayHello(); // Hello, Vue.js
      }
    }
  }
</script>

It is very simple to use, as long as you call the findComponentUpward method where you need it, the first parameter is generally passed in this, which is the context (instance) of the current component.

For comA in the above example, to be on the safe side, a layer of if (comA) is added to determine whether component A is found. If it is called without the specified component, an error will be reported.

findComponentUpward will only find the nearest component instance. If you want to find all components that meet the requirements, you need to use the following method.

Find all specified components up–findComponentsUpward

code show as below:

// assist.js
// From a component, find all the specified components upwards
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 [];
  }
}
export {<!-- --> findComponentsUpward };

Unlike findComponentUpward, findComponentsUpward returns an array containing all found component instances (note the extra “s” in the function name).

findComponentsUpward has few usage scenarios, and is generally only used in recursive components (introduced in the next section), because this function always looks for the parent (parent), and only the parent of the recursive component is itself. In fact, iView is also used in recursive component scenarios when using this method, such as the menu component Menu. Since recursive components are not commonly used in Vue.js components, naturally findComponentsUpward is not commonly used either.

Find the nearest specified component down – findComponentDownward

code show as below:

// assist.js
// From a component, find the nearest specified component down
function findComponentDownward (context, componentName) {<!-- -->
  const children = context. $children;
  let children = null;

  if (childrens. length) {<!-- -->
    for (const child of children) {<!-- -->
      const name = child. $options. name;

      if (name === componentName) {<!-- -->
        children = children;
        break;
      } else {<!-- -->
        children = findComponentDownward(child, componentName);
        if (children) break;
      }
    }
  }
  return children;
}
export {<!-- --> findComponentDownward };

context.$children gets all the subcomponents of the current component, so it needs to be traversed to find out whether there is a matching component name, if not found, continue to recursively find each $children of $children until the closest one is found.

Let’s look at an example, there are still two components A and B, A is the parent component of B, find B in A:

<!-- component-b.vue -->
<template>
  <div>
    Component B
  </div>
</template>
<script>
  export default {<!-- -->
    name: 'componentB',
    data () {<!-- -->
      return {<!-- -->
        name: 'Aresn'
      }
    },
    methods: {<!-- -->
      sayHello () {<!-- -->
        console.log('Hello, Vue.js');
      }
    }
  }
</script>
<!-- component-a.vue -->
<template>
  <div>
    Component A
    <component-b></component-b>
  </div>
</template>
<script>
  import componentB from './component-b.vue';
  import {<!-- --> findComponentDownward } from '../utils/assist.js';

  export default {<!-- -->
    name: 'componentA',
    components: {<!-- --> componentB },
    mounted () {<!-- -->
      const comB = findComponentDownward(this, 'componentB');
      if (comB) {<!-- -->
        console.log(comB.name); // Aresn
        comB.sayHello(); // Hello, Vue.js
      }
    }
  }
</script>

A and B in the example are parent-child relationships, so they can also be accessed directly with ref, but if it is not a parent-child relationship, and there are multiple generations in between, it is very convenient to use it.

Find all specified components down – findComponentsDownward

If you want to find all the specified components downward, you need to use the findComponentsDownward function, the code is as follows:

// assist.js
// From a component, find all specified components down
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);
  }, []);
}
export {<!-- --> findComponentsDownward };

There are many ways to implement this function. Here we cleverly use reduce as an accumulator, and use recursion to combine the found components into an array and return it. The amount of code is small, but it is a little difficult to understand.

The usage is similar to findComponentDownward, so I won’t write use cases anymore.

Find the sibling component of the specified component – findBrothersComponents

code show as below:

// assist.js
// From a component, find the sibling component of the specified component
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 {<!-- --> find Brothers Components };

Compared with the other 4 functions, findBrothersComponents has one more parameter exceptMe, whether to exclude itself, the default is true. The way to find sibling components is to first obtain context.$parent.$children, that is, all child components of the parent component, which currently contains itself, and all of them will have the third parameter exceptMe. When Vue.js renders components, it will add a built-in attribute _uid to each component. This _uid will not be repeated, so that we can exclude ourselves from a series of sibling components .

For example, component A is the parent of component B, find all sibling components in A in B (that is, all B components in A):

<!-- component-a.vue -->
<template>
  <div>
    Component A
    <component-b></component-b>
  </div>
</template>
<script>
  import componentB from './component-b.vue';
  
  export default {<!-- -->
    name: 'componentA',
    components: {<!-- --> componentB }
  }
</script>
<!-- component-b.vue -->
<template>
  <div>
    Component B
  </div>
</template>
<script>
  import {<!-- --> findBrothersComponents } from '../utils/assist.js';
  
  export default {<!-- -->
    name: 'componentB',
    mounted () {<!-- -->
      const comsB = findBrothersComponents(this, 'componentB');
      console.log(comsB); // ① [], empty array
    }
  }
</script>

At position ①, the printed content is an empty array, because there is only one B in A currently, and the third parameter of findBrothersComponents is true by default, which means excluding itself. If you write another B in A:

<!-- component-a.vue -->
<template>
  <div>
    Component A
    <component-b></component-b>
    <component-b></component-b>
  </div>
</template>

At this time, [VueComponent] will be printed out, and there is a component, but please note that it will be printed twice in the console, because two Bs are written in A, and console.log is defined in B, so both will be executed. If you understand this, you should understand that [VueComponent] is printed twice, which is another (if you don’t understand, think carefully pondering oh).

If you set the third parameter of findBrothersComponents in B to false:

// component-b.vue
export default {<!-- -->
  name: 'componentB',
  mounted () {<!-- -->
    const comsB = findBrothersComponents(this, 'componentB', false);
    console.log(comsB);
  }
}

At this point, [VueComponent, VueComponent] will be printed out, that is, it contains itself.

The above is the detailed introduction of the 5 functions. Once you get to these 5 functions, you will no longer have to worry about component communication in the future.

Conclusion

Only if you have seriously developed Vue.js independent components, you will understand the power of these 5 functions.

Extended reading

  • Syntax of ES6 Module

Note: Part of the code in this section refers to iView.