case

This time, I mainly take the business component that appeared in the article shared before. Last time, I just posted the code and did not say the implementation process in detail. This time, I will take this business component as the center and tell how to write a high-performance business component.






Analyze what the component is trying to do based on the diagram above

This similar strengthened version of the linkage between provinces and cities, you can view the selected provinces and cities, and the check box has three states, not selected, all selected, not all selected, the default state only shows the root data, click the corresponding option, if there is a subset will show the corresponding subset data.

Decomposition of demand

Here, the requirements will be decomposed first, step by step to achieve the function

  1. Realizing linkage between provinces and cities.
  2. Implement the full selection function, and if the subset has no options, the parent state changes to not select all.
  3. The checked item is displayed as a label, and the label has the function of removal, and the corresponding check box should also change the state.
  4. Implement value fetching.

The first step

Based on the observation that you can use a two-dimensional array, push the entire root directory into the array when you initialize it, push the subset into the array when you click on the option, and so on. Go straight to the code.

Typescript.

@Component({
    selector: 'directional-area-select',
    exportAs: 'directionalAreaSelect',
    templateUrl: './directional-select.component.html',
    styleUrls: ['./directional-select.component.less']})export class DirectionalSelectComponent implements OnInit{
    constructor() {
    }
    cacheList: any[] = [];
    _inputList;
    @Input('inputList') set inputList(value) {
        if(value instanceof Array && value.length){
            this._inputList = value;
            this.inputListChange();
        }
    }

    inputListChange() {
        if (this._inputList instanceof Array && this._inputList.length > 0) {
            this.cacheList.length = 0;
            this.cacheList.push(this._inputList); }}@param Index1 subscript of the current layer * @param index2 Subscript of the current layer * @param list of the current layer */
    pushCache(index1, index2, list) {
        // Select later
        let cl = this.cacheList[index1 + 1];
        let child = list[index2][this.child];
        if (child instanceof Array && child.length > 0) {
            if(! cl) {this.cacheList.push(child);
            } else {
                if(cl ! == child) {this.cacheList.splice(index1 + 1.1, child)
                }
            }
        } else {
            if(cl ! == child && ! (childinstanceof Array)) {
                this.cacheList.pop(); }}// Select forward
        if (child && child.length > 0) {
            while (this.cacheList.length > index1 + 2) {
                this.cacheList.pop(); }}}}Copy the code

The template:

<div class="select-list-inner">
    <div class="scope" *ngFor="let list of cacheList; let index1 = index" [ngStyle]="{' width. % ': 100.0 / cacheList length}">
        <ul class="list-with-select">
            <li class="spaui" *ngFor="let l of list; let index2 = index" (click)="pushCache(index1,index2,list)">
                <app-checkbox [(ngModel)]="l.selected" [label]="l.name" [checkState]="l.checkState"></app-checkbox>
                <i *ngIf="l[child]? .length > 0" class="icon yc-icon"> &#xe664; 
            </li>
        </ul>
    </div>
</div>
Copy the code


@input (‘inputList’) set inputList(value) {}, Input(‘inputList’) set inputList(value) {} If you look at pushCache, you can select either backwards or forwards, and you can tell what the user is doing by comparing the length of the array with the current index of the index that’s passed in.

There are three options for the future

  • The operation list of the same level does not appear in the next subset
  • Same-level operations on the list to appear the next level subset
  • The list of operations at the same level does not have subset data

The first case pushes the subset directly into the cacheList array

The second case replaces the data of the corresponding hierarchy with the content of the new subset

In the third case, it is determined that the lower-level data has value and the corresponding hierarchical list has no subset content. Remove the last item of the array

Select to directly determine the length of the cacheList and select the corresponding level subscript to remove the number of cachelists.

The second step

After analysis, all options have checkboxes, so each option can be selected all, unselected, or not selected all.

Three methods need to be added

As it changes, it also transfers state up and down.

CheckState 1 2 3 areaItemChange(data) {let child = data[this.child];
    if (data.selected) {
        data.checkState = 1
    } else{data.checkState = 3} // Look downifChild. The child && (length > 0) {enclosing recursionChildCheck (child)} / / up to find this. RecursionParentCheck (data); }Copy the code

Synchronizes the state of the subset with the parent state recursively

@param list */ private recursionChildCheck(list) {if (list && list.length > 0) {
        list.forEach(data => {
            let checked = data.parent.selected;
            data.selected = checked;
            if (checked) {
                data.checkState = 1;
                data.selected = true;
            } else {
                data.checkState = 3;
                data.selected = false;
            }
            let l = data[this.child];
            this.recursionChildCheck(l)
        })
    }
}
Copy the code

The final state of the parent level can be determined by calculating the selected state of subsets under the parent level, the number of length selected, and the number of length2 selected. The final state of the parent level can be determined by comparison, until the recursion to the root element.

@param data */ private recursionParentCheck(data) {/* recursionParentCheck(data) {let parent = data.parent;
    if (parent) {
        let l = parent[this.child];
        let length = l.reduce((previousValue, currentValue) => {
            returnpreviousValue + ((currentValue.selected) ? 1:0)}, 0);let length2 = l.reduce((previousValue, currentValue) => {
            returnpreviousValue + ((currentValue.checkState == 2) ? 1:0)}, 0);if (length == l.length) {
            parent.checkState = 1;
            parent.selected = true;
        } else if (length == 0 && length2 == 0) {
            parent.checkState = 3
        } else {
            parent.checkState = 2;
            parent.selected = false; } this.recursionParentCheck(parent); }}Copy the code

You need to change the inputListChange method

list
inputListChange() {
    if (this._inputList instanceof Array && this._inputList.length > 0) {
        this.list = this._inputList.map(d => {
            this.recursionChild(d);
            returnd; }); this.cacheList.length = 0; this.cacheList.push(this.list); }}Copy the code

/* recursion */ private recursionChild(target) {let list = target[this.child];
    if (list && list.length > 0) {
        list.forEach(data => {
            data.parent = target;
            this.recursionChild(data)
        })
    }
}
Copy the code

Create a parent field in each child element to save the parent content.

The third step

Gets the selected element, which is displayed as a label. If the parent state is selected all, it does not need to worry about subsets and displays the parent directly.

/** * Gets the selected element * When the parent state is selected all, subset elements are not considered. */ private recursionResult(list, result = [],type = 1) {
    if(list&& list.length > 0) {list.foreach (data => {// all selected and the parent has no check boxif ((data[this.hasCheckbox] && data.checkState == 1) || data.checkState == 2) {
                let child = data[this.child];
                if (child && child.length > 0) {
                    this.recursionResult(child, result, type); } // All checked and parent has check box result does not need to contain subset}else if(data.checkState == 1 && ! data[this.hasCheckbox]) { switch (type) {
                    case 1:
                        result.push(data.id);
                        break;
                    case 2:
                        result.push({
                            id: data.id,
                            name: data.name,
                        });
                        break;
                    case 3:
                        result.push(data);
                        break; }}})}return result;
}
Copy the code

Label Removal method

removeResultList(data) {
    data.selected = false;
    this.areaItemChange(data);
}
Copy the code

If you need to change the areaItemChange method, if you need to change the checkbox, you need to recalculate the value of the resultList, so that you can always operate on an object, change the corresponding label state, and the state of the list will also change.

resultList
areaItemChange(data) {
    if (data[this.hasCheckbox]) return;

    let child = data[this.child];

    if (data.selected) {
        data.checkState = 1
    } else{data.checkState = 3} // Look downifChild. The child && (length > 0) {enclosing recursionChildCheck (child)} / / up to find this. RecursionParentCheck (data); this.resultList = this.recursionResult(this.list,[],3); }Copy the code

The fourth step

The value obtained is the contents of the tag. • ˇ ‸ ˇ.) .


The advanced

Can change the component inspection strategy, sets the metadata changeDetection attribute to ChangeDetectionStrategy. OnPush only input attribute changes will trigger the check. Value type changes are also triggered, and reference types need reference changes to trigger checks.

A background thread can be used to process computationally heavy code, such as recursively binding the parent element.

private recursionChild(target) {
    let list = target[this.child];
    if(list && list.length > 0) { list.forEach(data => { data.parent = target; This. RecursionChild (data)}} / private recursionChildWorker(target,fn = ()=>{}){let fun = `
        onmessage = function (e) {
            let args = Array.from(e.data)
            let list = args[0];
            let key = args[1];
            function parent(target){
                let list = target[key];
                if (list && list.length > 0) {
                    list.forEach(data => {
                        data.parent = target;
                        this.parent(data);
                    })
                }
            }
            list.forEach(data => {
                parent(data);
            })
            postMessage(list);
        }
    `;
    const blob = new Blob([fun], {type: 'application/javascript'});
    const url = URL.createObjectURL(blob);
    const worker = new Worker(url);
    worker.postMessage([target, this.child]);
    worker.onmessage = () => {
        fn()
    }
}
Copy the code


After the

At this point, this component is complete. If there is a better way to write it, please leave a comment and discuss it.