baidu / san

A fast, portable, flexible JavaScript component framework
https://baidu.github.io/san/
MIT License
4.72k stars 549 forks source link

s-for指令通过数组下标访问元素存在bad case #703

Closed yinfangyan closed 2 years ago

yinfangyan commented 2 years ago

示例代码如下:

        const outer = ['one', 'two', 'three'];
        var MyApp = san.defineComponent({
            template: `
                <div s-for="item, idx in list" >
                    <p>item-{{list[idx]}}</p>
                </div>
            `,

            initData: function () {
                return {
                    list: [1,2]
                };
            },

            attached() {
                setTimeout(() => {
                console.log('before', outer)
                this.data.set('list', outer)
                this.nextTick(() => {
                    console.log('after', this.data.get('list'))
                })
            }, 1000)
            }
        });

        var myApp = new MyApp();
        myApp.attach(document.body);

实际渲染结果:

item-1
item-2
item-three

预期结果

item-one
item-two
item-three

测试了一下只有通过index访问数组获取的值有问题,若是直接使用 item,则结果符合预期

zhengmz commented 2 years ago

好象对数组的修改不能直接用 this.data.set('list', outer)

errorrik commented 2 years ago

一直没找到不那么碎片的时间跟跟看。应该不是 @zhengmz 说的原因。麻烦先用 item 规避

errorrik commented 2 years ago

翻了一遍实现,当时写的时候很多思考的点都浮上心头。

先说结论:这个 case,严格来说是 bug。但是不会修复,在当初实现的时候,就考虑到了的。下面是一些说明:

san 的视图更新机制是:将 数据变化对象 沿着组件树向下传递,树中每个节点决定自己子树如何更新。更详细的可以看 https://efe.baidu.com/blog/san-perf/

对于一个 for 节点,它会考虑要向子节点(每个item节点)传递的 数据变化对象 是什么。它向 item 节点传递的数据变化对象,一定是经过处理的。

为了达到较好的性能,for 节点在生成向 item 节点传递的数据变化对象时,有一些处理的考虑,尽量让中间运行的过程,负担小一些。

比如,对于这个 issue 的场景,我要引用当前数据项,我没必要额外在 item 之外,再增加一个 list[index] 的变更表达式信息。因为对于使用者来说,这么玩便利性其实是更差的,而且还带来额外的运行成本(不是实现成本,要实现的话很简单,代码就几行,在这敲这么多字,早实现完了)。

再比如,我引用数组对象上的非数字索引外的属性,更新也会有问题。因为对数组的 immutable 更新过程,如果要考虑 for length 遍历之外的东西,运行成本会很高,而且实际上不应该这么使用数组。所以下面的场景,{{list.p}} 的视图更新也会失败的。

    let outer = ['one'];
    outer.p = 'p2'

    var MyApp = san.defineComponent({
        template: `
            <div>
                <p s-for="item, idx in list"><u>item-{{list[idx]}}</u><b>{{list.p}}</b></p>
                <p s-for="item, idx in list"><u>item-{{item}}</u><b>{{list.p}}</b></p>
            </div>
        `,

        initData: function () {
            let list = [1];
            list.p = 'p';
            return {
                list
            };
        },

        attached() {
            setTimeout(() => {
            console.log('before', outer)
            this.data.set('list', outer)
            this.nextTick(() => {
                console.log('after', this.data.get('list'))
            })
        }, 1000)
        }
    });

    var myApp = new MyApp();
    myApp.attach(document.body);

借这个 issue,记录下一点之前实现时候思考过的东西。还有问题,我们再讨论

yinfangyan commented 2 years ago

好的,感谢您如此细致的答疑。

errorrik commented 2 years ago

看起来没有疑问,我就关了。如果再发现其他问题,我们再开新issue讨论