background

In the project, there is a custom component, when clicking on the child component, trigger the selection event, and through $emit, pass the child component’s data to the parent component, and there is an all select button to trigger all the child component’s selection. Originally, $emit was supposed to handle the sub-components and interact with the sub-components through $emit when modifying the state of the sub-components. However, when the amount of data exceeded a certain level, $emit affected the performance.

This is how you handle onSelect and selectionChange in component development.

Components in the Demo

The following code is a simple definition of the parent component, in the child component by listening to the change of data.isSelect, to trigger the selection event, imagine that in the process of using the component, you can still set the data.isSelect field, to update the component state, and trigger the selection.

The parent component primarily listens for the child component’s events, assembles selection, and continues to pass the child component’s events outward.

The parent component template

<template>
  <div>
    <button v-model="checkedAll" @click="onSelectAll">select all</button>
    <children
      v-for="item of children"
      @onSelect="onSelect"
      :data="item"
    ></children>
  </div>
</template>
Copy the code

The parent component script

export default {
  data() {
    return {
      checkedAll: false.children: [].selection: [],}; },methods: {
    onSelectAll() {
      this.children.forEach((child) = > {
        child.isSelect = true;
      });
    },
    onSelect(child, selectStatus) {
      if (selectStatus) {
        this.selection.push(child);
      } else {
        this.selection = this.selection.filter(item= >item ! = child); }this.$emit('onSelect', child, selectStatus);
      this.$emit('selectionChange'.this.selection); }},}Copy the code

The child component is responsible for the presentation of data and the handling of selected events. In the demo, there is only one parent component, but in a real project, two different types of parent components are used, which is a good example of pulling common logic out.

Child component template

<template>
  <div @click="onSelect">
    <span>{{ data.isSelect ? 'checked' : 'unchecked' }}</span>
    <span>{{ data.name }}</span>
  </div>
</template>
Copy the code

Subcomponents script

export default {
  props: ['data'].methods: {
    onSelect() {
      this.data.isSelect = !this.data.isSelect; }},watch: {
    'data.isSelect': function() {
      this.$emit('onSelect'.this.data, this.data.isSelect); }},}Copy the code

Conflict and Handling

There is no problem with this design. However, if there is a lot of data, such as 2000+, all the $emit events will be handed over to Watch. Each $emit event will only have hundreds of ms, but the whole process will take several minutes.

If there is no solution, choose to refer to excellent open source projects. In the source code of El-Table, the source code of toggleRowSelection is as follows. The emitChange parameter is used to control whether the event is triggered or not, which not only ensures the reuse of the same logic, but also controls the performance impact caused by $emit. We don’t know if element-UI is meant to control performance, but in actual testing, for tables with large amounts of data, the selected event responded well, so we used this idea to add control event-related code to the component code.

toggleRowSelection(row, selected, emitChange = true) {
  const changed = toggleRowStatus(this.states.selection, row, selected);
  if (changed) {
    const newSelection = (this.states.selection || []).slice();
    // Call the API to change the selected value without triggering the SELECT event
    if (emitChange) {
      this.table.$emit('select', newSelection, row);
    }
    this.table.$emit('selection-change', newSelection); }},Copy the code

Add Event control

Because the parent component does not call the API of the child component to modify the state of the child component, we choose to add a field in the data of the child component to control the state of the child component. The modified code is as follows:

The parent component onSelectAll

onSelectAll() {
  this.children.forEach((child) = > {
    child.updateByComponent = true;
    child.isSelect = true;
    this.$nextTick((a)= > {
      child.updateByComponent = false;
    });
  });
}
Copy the code

Children watch

watch: {
  'data.isSelect': function() {
    if (this.data.updateByComponent) return;
    this.$emit('onSelect'.this.data, this.data.isSelect); }},Copy the code

The actual effect

The revised code reduces the response time from more than 4 minutes to 400 milliseconds, and the actual experience is still good. In the case of the same amount of data, the selected effect takes effect almost immediately.

conclusion

This is a reflection of one-time optimization in the project, and also a process of reading the source code of the open source project. Although THE components are written by myself and the holes are dug by myself, this experience is also a growth in component encapsulation. If you have any good suggestions, feel free to post them in the comments.


subsequent

After writing the minimal demo, the all-selected response was in the second level, but in the project, updating isSelect only involved a single DOM update, and it is unknown why this effect occurred. To understand it, it takes time to try it out.

Minimum Demo address