You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

6.0 KiB

computed进行列表组件的状态控制

开发中我们经常使用computed计算属性控制状态,如列表刷新,列表选中等. 正确理解computed原理并灵活使用

看如下示例,把选中状态和列表节点放入同一个computed属性中计算,会导致什么问题呢?

    computed: {
        listItems: function () {
            return this.model.items.map(item => {
                return {
                    ...item,
                    type: "bi.text_item",
                    cls: "bi-list-item-active2",
                    selected: item.value === this.model.selectedValue,
                };
            });
        },
    }

下面书写了一个简单的demo,可以看到,每次改变selectedValue状态,都会触发列表的重新渲染.当列表节点过多,浏览器性能较差的时候,会出现明显的闪烁或者卡顿.

究其原因,是因为listItems属性的计算同时依赖items和selectedValue属性,每当selectedValue属性改变都会触发listItems的重新计算,返回值是一个新的数据,因此触发watch,重新populate

let listModel = BI.inherit(Fix.Model, {
    state: function () {
        return {
            selectedValue: 1,
            items: [
                {
                    text: 1,
                    value: 1,
                }, {
                    text: 2,
                    value: 2,
                }, {
                    text: 3,
                    value: 3,
                }, {
                    text: 4,
                    value: 4,
                },
            ],
        };
    },

    computed: {
        listItems: function () {
            return this.model.items.map(item => {
                return {
                    ...item,
                    type: "bi.text_item",
                    cls: "bi-list-item-active2",
                    selected: item.value === this.model.selectedValue,
                };
            });
        },
    },

    actions: {
        changeSelectedValue: function (value) {
            this.model.selectedValue = value;
        },

        addItem: function () {
            this.model.items.push({
                text: this.model.items.length + 1,
                value: this.model.items.length + 1,
            });
        },
    },
});

BI.model("list", listModel);

let list = BI.inherit(BI.Widget, {

    _store: function () {
        return BI.Models.getModel("list");
    },

    watch: {
        listItems: function (items) {
            BI.Msg.toast("populate");
            this.list.populate(items);
        },
    },

    render: function () {
        return {
            type: "bi.vertical",
            vgap: 15,
            width: 300,
            items: [
                {
                    type: "bi.button_group",
                    ref: ref => this.list = ref,
                    layouts: [
                        {
                            type: "bi.vertical",
                        },
                    ],
                    items: this.model.listItems,
                    listeners: [
                        {
                            eventName: "EVENT_CHANGE",
                            action: (value) => {
                                this.store.changeSelectedValue(value);
                            },
                        },
                    ],
                }, {
                    type: "bi.button",
                    text: "选中下一个节点",
                    handler: () => {
                        this.store.changeSelectedValue(this.model.selectedValue + 1);
                    },
                }, {
                    type: "bi.button",
                    text: "添加一个节点",
                    handler: () => {
                        this.store.addItem();
                    },
                },
            ],

        };
    },
});

BI.shortcut("list", list);


const ListWidget = BI.createWidget({
    type: "bi.vertical",
    items: [
        {
            type: "list",
        },
    ],
});

return ListWidget;

示例

要如何避免这类问题呢?其实就一句话,将选中状态与列表节点状态分离开.在view层进行控件层面的属性控制

例如,computed属性中不再控制选中状态,改为单独watch选中状态,调用组件的setValue api 设置选中状态,此时两个状态分离,仅改变选中状态不会触发列表的重新渲染

    computed: {
        listItems: function () {
            return this.model.items.map(item => {
                return {
                    ...item,
                    type: "bi.text_item",
                    cls: "bi-list-item-active2",
                    // selected: item.value === this.model.selectedValue,
                };
            });
        },
    }

    watch: {
        selectedValue: function (value) {
            this.list.setValue(value);
        },

        listItems: function (items) {
            BI.Msg.toast("populate");
            this.list.populate(items);
        },
    },

为了列表重新渲染之后依旧保持选中值,一般有两种处理方式

  1. populate之后调用 setValue
  2. 在populate之前进行一次遍历,设置selected属性
    watch: {
        listItems: function (items) {
            this.list.populate(items);
            this.list.setValue(this.model.selectedValue);
        },
    },
    watch: {
        listItems: function (items) {
            this.list.populate(items.map(item => ({ ...item, selected: item.value === this.model.selectedValue })));
        },
    },

利用响应式新特性,可以更加逻辑清晰的进行选中和列表渲染控制 不过响应式并不能解决populate之后状态保持问题,因此需要在populate之前额外设置selected属性

    {
        type: "bi.button_group",
        ref: ref => this.list = ref,
        layouts: [
            {
                type: "bi.vertical",
            },
        ],
        items: () => this.model.listItems,
        value: () => this.model.selectedValue,
    },