Polymer 2.x: dom-repeat 不观察计算数组子项的变化

Polymer 2.x: dom-repeat does not observe changes to computed array's children

我有一个 dom-repeat 和另一个 dom-repeat 嵌套在里面。外层绑定到一个计算数组。当我对父数组进行更改时,会观察到更改(意味着触发观察器代码),但更改不会反映在 UI/dom-repeat 中。

这是我的代码示例。单击第二个 dom-repeat 内的 div 会触发函数 handleItemTap,然后该函数会更改父数组。然后观察到这种变化,并重新计算计算出的数组;但是,将 IsChecked 添加到父对象不会影响 UI 中的复选框,它应该绑定到 IsChecked 属性。我做错了什么?

<template is="dom-repeat"
          items="[[outerComputedArray]]" as="group"
          id="groupRepeater">
    <div class="group-container">       
        <template is="dom-repeat"
                  items="[[group.InnerArray]]" as="sesh">
            <div on-tap="handleItemTap">
                <paper-checkbox id="paperCheckboxId"
                                checked="[[sesh.IsChecked]]"></paper-checkbox>
            </div>
        </template>
    </div>
</template>

...

static get properties() {
    return {
        'parentArray': Array,
        'outerComputedArray': {
            type: Array,
            computed: 'computeOuterArray(parentArray.*)'
        }
    }
}

computeOuterArray(parentArray) {
    if (!(typeof parentArray.base === 'undefined' || parentArray.base == null || parentArray.base.length == 0)) {
        let groupedGrid = parentArray.base.reduce((a, b) => {
            a = a || [];
            let idx = a.findIndex(x => x.PaymentType == b.PaymentType);
            if (idx == -1) {
                let newArray = [];
                newArray.push(b);
                a.push({ 'PaymentType': b.PaymentType, 'InnerArray': newArray });
            }
            else {
                a[idx].InnerArray.push(b);
            }
            return a;
        }, []).map(x => {
            x.Total = x.InnerArray.map(y => y.TotalAmount).reduce((a, b) => a + b, 0);
            return x;
        });
        groupedGrid.sort((a, b) => a.PaymentType.localeCompare(b.PaymentType));

        return groupedGrid;
    }

    return [];
}

handleItemTap(e) {
    let idx = this.parentArray.findIndex(x => x.id == e.model.sesh.id);
    this.set(`parentArray.${idx}.IsChecked`, !(!!e.model.sesh.IsChecked));
}

看来 Polymer 对象 dirty-checking 是问题所在。它用于加速 Polymer,但有时 dirty-checking 不起作用(例如,当您更改数组中的元素时)。 要绕过它,您需要使用 mutable data mixin.

首先,您需要在声明元素时引入mixin。

class MyMutableElement extends Polymer.MutableData(Polymer.Element) { ... }

您还需要将 mutable-data 标记添加到您的 dom-repeat。

<template is="dom-repeat" items="[[outerComputedArray]]" as="group" id="groupRepeater" mutable-data>

如果它仍然不起作用,请尝试在 computeOuterArray 函数的末尾添加类似这样的内容(但前提是您确实更改了数组)。

timeout(() => { this.notifyPath('outerComputedArray') }, 10)

非常感谢@mary-shaw 的帮助,但我无法让这些建议发挥作用。这是我现在想出的。我在 outerComputedArray 上放置了一个观察者,用于更新第二个数组——一个名为 selectedIds 的新 属性——包含 属性 IsChecked 的任何项目已经变了。然后我添加了另一个计算函数来检查 selectedIds 是否与最里面的对象 dom-repeat.

具有相同的 id 属性
<template is="dom-repeat"
          items="[[outerComputedArray]]" as="group">
    <div class="group-container">       
        <template is="dom-repeat"
                  items="[[group.InnerArray]]" as="sesh">
            <div on-tap="handleItemTap">
                <paper-checkbox id="paperCheckboxId"
                                checked="[[computeIsChecked(selectedIds.*, sesh.id)]]"></paper-checkbox>
            </div>
        </template>
    </div>
</template>

...
<script>
    static get properties() {
        return {
            'parentArray': Array,
            'outerComputedArray': {
                type: Array,
                computed: 'computeOuterArray(parentArray.*)'
            },
            'selectedIds': Array
        }
    }

    static get observers() {
        return [
            'onOuterComputedArrayChanged(outerComputedArray.*)'
        ]
    }

    onOuterComputedArrayChanged(outerComputedArray) {
        if (!(typeof outerComputedArray.base === 'undefined' || outerComputedArray.base == null || outerComputedArray.base.length == 0)) {
            this.set('selectedIds', outerComputedArray.base.filter(x => x.InnerArray.some(y => !!y.IsChecked)).map(x => x.InnerArray.filter(y => !!y.IsChecked)).reduce((a, b) => a.concat(b), []).map(x => x.id));
        }
    }

    computeIsChecked(selectedIds, id) {
        if (!(typeof selectedIds.base === 'undefined' || selectedIds.base == null || selectedIds.base.length == 0)) {
            return selectedIds.base.some(x => x == id);
        }

        return !!0;
    }

    computeOuterArray(parentArray) {
        if (!(typeof parentArray.base === 'undefined' || parentArray.base == null || parentArray.base.length == 0)) {
            let groupedGrid = parentArray.base.reduce((a, b) => {
                a = a || [];
                let idx = a.findIndex(x => x.PaymentType == b.PaymentType);
                if (idx == -1) {
                    let newArray = [];
                    newArray.push(b);
                    a.push({ 'PaymentType': b.PaymentType, 'InnerArray': newArray });
                }
                else {
                    a[idx].InnerArray.push(b);
                }
                return a;
            }, []).map(x => {
                x.Total = x.InnerArray.map(y => y.TotalAmount).reduce((a, b) => a + b, 0);
                return x;
            });
            groupedGrid.sort((a, b) => a.PaymentType.localeCompare(b.PaymentType));

            return groupedGrid;
        }

        return [];
    }

    handleItemTap(e) {
        let idx = this.parentArray.findIndex(x => x.id == e.model.sesh.id);
        this.set(`parentArray.${idx}.IsChecked`, !(!!e.model.sesh.IsChecked));
    }
</script>