Vue.js 计算 属性 在通过事件时失去其反应性
Vue.js computed property loses its reactivity when passed through an event
我的主应用程序中有一个 Modal
组件,每当必须显示模式时,它都会通过事件获取传递的内容。模态内容始终是一个列表,其中包含与每个项目关联的操作,例如 "select" 或 "remove":
Vue.component('modal', {
data() {
return {
shown: false,
items: [],
callback: ()=>{}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button @click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
});
遗憾的是,当像下面的组件一样将计算的 属性 传递给该模态时,反应式 link 被破坏 -> 如果操作是 "remove",则列表不是已更新。
Vue.component('comp', {
data() {
return {obj: {a: 'foo', b: 'bar'}}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<button @click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', this.objKeys, this.remove);
}
}
});
查看此 fiddle 中的最小用例:https://jsfiddle.net/christophfriedrich/cm778wgj/14/
我认为这是一个错误 - Vue 不应该记住 objKeys
用于在 Modal
中进行渲染并更新它吗? (将 obj
的更改转发到 objKeys
有效。)如果没有,我哪里错了,我怎样才能达到我想要的结果?
您正在将值传递给函数,而不是将 prop 传递给组件。道具是反应性的,但价值只是价值。您在 comp
的模板中包含 modal
,因此对其进行修改以(至少)将 items
作为道具。然后它会反应。
我建议让删除过程遵循 emit-event-and-process-in-parent 而不是传递回调。
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button @click="remove('a')">Remove a</button>
<button @click="remove('b')">Remove b</button>
<button @click="showModal">Show Modal</button>
<modal :items="objKeys" event-name="remove" @remove="remove"></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal');
}
}
});
Vue.component('modal', {
props: ['items', 'eventName'],
data() {
return {
shown: false,
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button @click="emitEvent(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
methods: {
show(items, callback) {
this.shown = true;
},
emitEvent(item) {
this.$emit(this.eventName, item);
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>
您的模态框使用它自己的 items
副本:
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button @click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
该副本在调用 show
时进行一次,您正在复制的只是在您发出 showModal
事件时计算的值。 show
收到的不是计算的,它分配的也不是计算的。这只是一个值。
如果您在代码的任何地方进行了类似
的赋值
someDataItem = someComputed;
数据项不是计算的功能副本,而是赋值时其值的快照。这就是为什么在 Vue 中复制值是一种不好的做法:它们不会自动保持同步。
您可以传递一个 returns 感兴趣的值的函数,而不是到处复制值;实际上是一个 get 函数。为了语法清晰,您可以根据该函数进行计算。那么你的代码就变成了
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button @click="remove('a')">Remove a</button>
<button @click="remove('b')">Remove b</button>
<button @click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', () => this.objKeys, this.remove);
}
}
});
Vue.component('modal', {
data() {
return {
shown: false,
getItems: null,
callback: () => {}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button @click="callback(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
computed: {
items() {
return this.getItems && this.getItems();
}
},
methods: {
show(getItems, callback) {
this.shown = true;
this.getItems = getItems;
this.callback = callback;
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>
我的主应用程序中有一个 Modal
组件,每当必须显示模式时,它都会通过事件获取传递的内容。模态内容始终是一个列表,其中包含与每个项目关联的操作,例如 "select" 或 "remove":
Vue.component('modal', {
data() {
return {
shown: false,
items: [],
callback: ()=>{}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button @click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
});
遗憾的是,当像下面的组件一样将计算的 属性 传递给该模态时,反应式 link 被破坏 -> 如果操作是 "remove",则列表不是已更新。
Vue.component('comp', {
data() {
return {obj: {a: 'foo', b: 'bar'}}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<button @click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', this.objKeys, this.remove);
}
}
});
查看此 fiddle 中的最小用例:https://jsfiddle.net/christophfriedrich/cm778wgj/14/
我认为这是一个错误 - Vue 不应该记住 objKeys
用于在 Modal
中进行渲染并更新它吗? (将 obj
的更改转发到 objKeys
有效。)如果没有,我哪里错了,我怎样才能达到我想要的结果?
您正在将值传递给函数,而不是将 prop 传递给组件。道具是反应性的,但价值只是价值。您在 comp
的模板中包含 modal
,因此对其进行修改以(至少)将 items
作为道具。然后它会反应。
我建议让删除过程遵循 emit-event-and-process-in-parent 而不是传递回调。
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button @click="remove('a')">Remove a</button>
<button @click="remove('b')">Remove b</button>
<button @click="showModal">Show Modal</button>
<modal :items="objKeys" event-name="remove" @remove="remove"></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal');
}
}
});
Vue.component('modal', {
props: ['items', 'eventName'],
data() {
return {
shown: false,
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button @click="emitEvent(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
methods: {
show(items, callback) {
this.shown = true;
},
emitEvent(item) {
this.$emit(this.eventName, item);
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>
您的模态框使用它自己的 items
副本:
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button @click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
该副本在调用 show
时进行一次,您正在复制的只是在您发出 showModal
事件时计算的值。 show
收到的不是计算的,它分配的也不是计算的。这只是一个值。
如果您在代码的任何地方进行了类似
的赋值someDataItem = someComputed;
数据项不是计算的功能副本,而是赋值时其值的快照。这就是为什么在 Vue 中复制值是一种不好的做法:它们不会自动保持同步。
您可以传递一个 returns 感兴趣的值的函数,而不是到处复制值;实际上是一个 get 函数。为了语法清晰,您可以根据该函数进行计算。那么你的代码就变成了
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button @click="remove('a')">Remove a</button>
<button @click="remove('b')">Remove b</button>
<button @click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', () => this.objKeys, this.remove);
}
}
});
Vue.component('modal', {
data() {
return {
shown: false,
getItems: null,
callback: () => {}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button @click="callback(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
computed: {
items() {
return this.getItems && this.getItems();
}
},
methods: {
show(getItems, callback) {
this.shown = true;
this.getItems = getItems;
this.callback = callback;
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>