当用较短的数组替换时,Vue.js组件数组被误导

时间:2018-04-19 02:28:00

标签: javascript vue.js vue-reactivity

我将自己介绍给 Vue.js(版本2.5.16),但遇到了解决反应性的问题的问题:< / p>

我有两个组件列表,其中v-for循环可以在大多数情况下正确呈现 - 我可以与组件交互并添加新组件。这些组件与代表区域的REST API交互,并且我希望它的功能相当明显 - 它主要返回单个或所有区域的JSON表示&#39;例如:

{
    "state": "off",
    "pin": 24,
    "uri": "/zones/6",
    "name": "extra"
}

每当添加或删除区域时,应用都会重新加载整个区域列表。但是,出于某种原因,当我删除区域并触发重新加载区域时,列表会使用错误的数据进行渲染!无论我删除哪个区域,列表的最后一项似乎都会弹出&#39;而不是新列表中未显示的预期区域!当我检查app.zones数据时, Javascript数据中的所有内容都显示正确但它只是没有在浏览器中显示。

以下是相关代码:

...
<div class="tab-content">
    <div id="control" class="tab-pane fade in active">
        <ul>
            <zone-control
                v-for="zone in zones"
                v-bind:init-zone="zone"
                v-bind:key="zone.id">
            </zone-control>
        </ul>
    </div>
    <div id="setup" class="tab-pane fade">
        <table class="table table-hover">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Pin</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <tr is="zone-setup"
                    v-for="zone in zones"
                    v-bind:init-zone="zone"
                    v-bind:key="zone.id">
                </tr>
                <tr is="zone-add"></tr>
            </tbody>
        </table>
    </div>
</div>
...
<script>
    Vue.component('zone-control', {
        props: ['initZone'],
        template:
            `<li>
                <div class="btn btn-default" v-on:click="toggleState">
                    <span v-if="zone.state == 'on'" class="glyphicon glyphicon-ok-circle text-success"></span>
                    <span v-else class="glyphicon glyphicon-remove-sign text-danger"></span>
                    Zone: {{ zone.name }}
                </div>
            </li>`,
        data: function() {
            return {
                zone: this.initZone
            };
        },
        methods: {
            toggleState: function() {
                var state = (this.zone.state == 'on' ? 'off' : 'on');
                console.log('Turning zone ' + this.zone.name + ' ' + state);
                var comp = this
                fetch(
                    this.zone.uri,
                    {method: 'PUT',
                    headers: new Headers({
                        'Content-Type': 'application/json'
                    }),
                    body: JSON.stringify({state: state})
                }).then( function(result) {
                    return result.json()
                }).then( function(data) {
                    comp.zone = data;
                });
            }
        }
    })

    Vue.component('zone-setup', {
        props: ['initZone'],
        template:
            `<tr>
                <td>{{ zone.name }}</td>
                <td>{{ zone.pin }}</td>
                <td><div v-on:click="deleteZone" class="btn btn-danger btn-small"></div></td>
            </tr>`,
        data: function() {
            return {
                zone: this.initZone
            };
        },
        methods: {
            deleteZone: function() {
                fetch(
                    this.zone.uri,
                    {method: 'DELETE'}
                ).then( function(result) {
                    app.load_zones();
                });
            }
        }
    })

    Vue.component('zone-add', {
        template:
            `<tr>
                <td><input v-model="zone.name" type="text" class="form-control"></input></td>
                <td><input v-model="zone.pin" type="text" class="form-control"></input></td>
                <td><div v-on:click="addZone" class="btn btn-success btn-small"></div></td>
            </tr>`,
        data: function() {
            return {
                zone: {
                    name: '',
                    pin: ''
                }
            };
        },
        methods: {
            addZone: function() {
                console.log('Adding zone ' + this.zone.name);
                var comp = this
                fetch(
                    "/zones",
                    {
                        method: 'POST',
                        headers: new Headers({
                            'Content-Type': 'application/json'
                        }),
                        body: JSON.stringify({
                            name: comp.zone.name,
                            pin: comp.zone.pin})
                    }
                ).then( function(result) {
                    app.load_zones()
                    comp.zone = {}
                });
            }
        }
    })

    var app = new Vue({
        el: '#app',
        data: {
            zones: []
        },
        methods: {
            load_zones: function() {
                fetch("zones")
                .then(function(result){
                    return result.json()
                }).then(function(data){
                    app.zones = data;
                });
            }
        },
        created: function() {
            this.load_zones();
        }
    })
</script>

我已经阅读了几篇参考文献,似乎有一些边缘案例或其他“gatchas”#39;但这似乎不属于任何一个。例如,this other question似乎同意在Vue.js反应系统的约束下更换整个数组是一种非常简单的改变数组的方法。

我已经检查过的 Vue.js文档的其他一些链接,但这些链接似乎与我的问题无关:

Reactivity in Depth

List Rendering

Common Beginner Gotchas

有关上下文中的完整代码,请参阅github repo here

更新

根据评论,我用简化的JSBin复制了这些症状。单击任何按钮以查看,当应删除第二个元素时,最后一个元素将被删除!

1 个答案:

答案 0 :(得分:0)

好的,我弄清楚了:它是v-bind:key的全部内容。我意识到我已经绑定了一个属性,该属性不是由我提供数据的API提供的。引用每个组件的(真实的)唯一属性,问题就消失了。

我注意到List Rendering Documentation提及了

  

在2.2.0+中,当对组件使用v-for时,现在需要一个键。

有趣的是,我上面链接的JSBin显着使用 Vue.js版本2.0.3 ,但问题仍然存在,并通过正确使用key属性来解决在v-for循环中。

如果有其他人想看到这一点,请查看问题中关联的JSBin,并在v-bind:key="zone.id"元素中添加<tr>。之后问题就解决了。