0

我正在向Vue.js(2.5.16 版)介绍自己,但在理解Reactivity的问题时遇到了问题:

我有两个组件列表,其中v-for大部分时间都正确呈现循环 - 我可以与组件交互并添加新组件。这些组件与表示“区域”的 REST API 交互,我希望其功能相当明显 - 它主要返回单个或所有“区域”的 JSON 表示,例如:

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

每当添加或删除区域时,应用程序都会重新加载整个区域列表。但是,由于某种原因,当我删除一个区域并触发重新加载区域时,列表会使用错误的数据呈现!无论我删除哪个区域,列表的最后一项似乎都“弹出”了,而不是预期的区域没有出现在新列表中!当我检查 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>

我已经阅读了几篇参考资料,似乎有一些边缘案例或其他“问题”,但这似乎不属于其中任何一个。例如,另一个问题似乎同意替换整个数组是在 Vue.js 反应性系统的约束内更改数组的一种非常简单的方法。

我检查过的其他一些指向Vue.js 文档的链接是相关的,但似乎并不指向我的问题:

深度反应

列表渲染

常见的初学者陷阱

有关上下文中的完整代码,请参阅此处的 github 存储库。

更新

根据评论,我在简化的JSBin中重现了这些症状。单击任何按钮可以看到,当第二个元素应该被删除时,最后一个元素会被删除!

4

1 回答 1

0

好的,我想通了:这都是关于v-bind:key. 我意识到我正在绑定到我从中获取数据的 API 未提供的属性。将其引用到每个组件的(真实和)唯一属性,问题就消失了。

我确实注意到列表渲染文档提到

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

有趣的是,JSBin我上面链接的明显使用了 Vue.js 版本 2.0.3,但问题仍然存在,并且通过在循环中正确使用key属性得到修复。v-for

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

于 2018-04-19T04:05:49.453 回答