Vue组件新增数据变更不生效问题的原因与解决方案
问题场景
你遇到的情况是这样的:在Vue组件里点击「New Data」按钮给现有数据新增属性后,点击复选框绑定的item.show属性变更没法实时更新视图,但点击「Greeting」按钮修改原本就存在的msg数据时,视图却能正常更新。
先贴出你的代码方便对照:
JS代码
Vue.component('fighters', { template: '#fighters-template', data() { return { items: [ { name: 'Ryu' }, { name: 'Ken' }, { name: 'Akuma' } ], msg: 'hi' } }, methods: { newData() { this.items.forEach(item => { item.id = Math.random().toString(36).substr(2, 9); item.game = 'Street Figther'; item.show = false; }); this.items.push() // 这里是无效代码,没有传入任何元素 }, greeting() { this.msg = 'hola' } } }); new Vue({ el: '#app' })
HTML代码
<main id="app"> <fighters></fighters> </main> <template id="fighters-template"> <div class="o-container--sm u-my1"> <ul> <li v-for="item in items"> <input type="checkbox" v-model="item.show"> {{ item.name }}</li> </ul> <button class="c-button c-button--primary" @click="newData()">New Data</button> <h2>{{ msg }}</h2> <button class="c-button c-button--primary" @click="greeting()">Greeting</button> <hr> <pre>{{ items }}</pre> </div> </template>
问题根源
这是Vue 2的响应式机制导致的:Vue在实例初始化时,只会对data中已存在的属性进行响应式绑定。你在newData方法里给每个item新增的id、game、show属性,都是在实例创建后才动态添加的,Vue没有办法自动追踪这些新增属性的变化,所以当你点击复选框修改item.show时,数据本身其实变了,但视图不会同步更新。
而msg是一开始就定义在data里的属性,Vue已经为它建立了响应式依赖,所以修改它能立刻触发视图更新。另外你的this.items.push()是无效代码,没有传入任何元素,不过这不是导致响应式问题的原因。
解决方案
有两种靠谱的解决方式,你可以根据自己的场景选择:
方法1:提前定义所有需要的响应式属性
在初始化items数组时,就把后续需要用到的id、game、show属性都定义好,哪怕给个默认值。这样Vue在实例创建时就会给这些属性绑定响应式:
data() { return { items: [ { name: 'Ryu', id: '', game: '', show: false }, { name: 'Ken', id: '', game: '', show: false }, { name: 'Akuma', id: '', game: '', show: false } ], msg: 'hi' } }
之后newData方法只需要赋值即可,不需要新增属性:
newData() { this.items.forEach(item => { item.id = Math.random().toString(36).substr(2, 9); item.game = 'Street Figther'; item.show = false; }); // 如果需要新增元素,可以这样写:this.items.push({ name: 'Chun-Li', id: '', game: '', show: false }) }
方法2:使用this.$set添加响应式属性
如果没办法提前定义属性,Vue提供了this.$set(全局方法是Vue.set)来手动给对象添加响应式属性。修改你的newData方法:
newData() { this.items.forEach(item => { this.$set(item, 'id', Math.random().toString(36).substr(2, 9)); this.$set(item, 'game', 'Street Figther'); this.$set(item, 'show', false); }); // 去掉无效的this.items.push(),或者按需添加新元素 }
this.$set会告诉Vue:“我要给这个对象加个响应式属性,请帮我处理一下”,这样新增的show属性就能被Vue追踪变化,复选框的修改也会实时反映在视图里了。
验证效果
修改后再点击「New Data」,然后操作复选框,你会看到<pre>标签里的items数据会实时更新,视图也会同步响应。
内容的提问来源于stack exchange,提问作者Julio Cesar Montenegro

