我做了我的第一个Vue项目,一切都很好,所以我添加了Vuex(将来不会是多余的,我为了兴趣而尝试过),一切仍然OK,直到我启用严格模式。事实证明,组件会在突变之外改变存储状态,但是我不想要它,并且已经使用 data()
在组件中创建了 nessesary 对象的本地副本。
我的目的是在父组件中创建一个本地对象,然后使用 v-model
将其属性传递给子组件(我知道这是 v- 的事件驱动语法糖)绑定(bind)
和v-on:input
),当本地对象更新时(通过v-model
在子对象内部),父组件的方法会调度 Action 储藏。相反,由于外部变异,我收到一条错误消息,而且它只发生在第二个和后续输入事件时。
此外,如果我在 ProdutRow 组件观察器中替换这些行,它也会起作用:
item: {
handler(value) {
this.$store.dispatch('updateProduct', {
product: value,
index: this.index,
});
},
deep: true,
}
with: product: {...value},
或 Object.assign({}, value)
,但商店操作中的相同代码不会:它抛出同样的错误。
data()
不会创建指定 prop 的副本吗?如果是这样,为什么 Object.assign 在商店中不起作用?
代码:
store.js
import Vue from 'vue';
import Vuex from 'vuex';
import {ADD_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT, UPDATE_FORM, UPDATE_PREPAYMENT_IN_PERCENT} from './mutation-types';
import _ from 'lodash';
Vue.use(Vuex);
let id = 1;
const product = {
order: id++,
name: '',
size: '',
content: '',
price: 0,
number: 1,
discount: '',
guarantee: 0,
promotion: 0,
location: '',
sum: 0,
};
export default new Vuex.Store({
strict: true,
state: {
products: [
Object.assign({}, product),
],
form: {
prepaymentInPercent: 100,
}
},
getters: {
total(state) {
return state.products.reduce(function (acc, cur, index, array) {
return array.length > 1 ? acc + cur.sum : cur.sum;
}, 0);
},
rest(state, getters) {
return getters.total - getters.prepaymentInRub;
},
prepaymentInRub(state, getters) {
return getters.total * state.form.prepaymentInPercent / 100;
}
},
mutations: {
[ADD_PRODUCT](state, product) {
state.products.push(product);
},
[UPDATE_PRODUCT](state, {product, index}) {
state.products.splice(index, 1, product);
},
[DELETE_PRODUCT](state, index) {
state.products.splice(index, 1);
},
[UPDATE_FORM](state, form) {
state.form = form;
},
[UPDATE_PREPAYMENT_IN_PERCENT](state, percent) {
state.form.prepaymentInPercent = percent;
}
},
actions: {
addProduct({commit}) {
let newProduct = Object.assign({}, product);
newProduct.order = id++;
commit(ADD_PRODUCT, newProduct);
},
updateProduct: _.debounce(function ({commit}, product) {
commit(UPDATE_PRODUCT, product);
}, 1),
deleteProduct({commit, state}, index) {
state.products.length > 1 && commit(DELETE_PRODUCT, index)
},
updatePrepaymentInPercentByRub({commit, getters}, rubles) {
let percent = Math.round(rubles / getters.total * 100);
commit(UPDATE_PREPAYMENT_IN_PERCENT, percent);
}
},
});
ProductTable.vue
<template>
<table border="0">
<thead>
<tr>
<th class="pointer" @click="addProduct">+</th>
<th>Номер</th>
<th>Название</th>
<th>Размер</th>
<th>Наполнение</th>
<th>Цена</th>
<th>Количество</th>
<th>Скидка</th>
<th>Акция</th>
<th>Сумма</th>
<th>Гарантия</th>
<th>Заказ</th>
<th class="pointer" @click="toJSON">JSON</th>
</tr>
</thead>
<tbody>
<template v-for="(product, index) in products">
<ProductRow
:initialItem="product"
:key="product.order"
:index="index"
/>
</template>
<tr>
<td colspan="12">{{total}}</td>
<td>{{json}}</td>
</tr>
</tbody>
</table>
</template>
<script>
import ProductRow from './ProductRow';
import {mapGetters, mapActions, mapState} from 'vuex';
export default {
components: {
ProductRow,
},
name: 'ProductTable',
data() {
return {
json: '',
};
},
computed: {
...mapState(['products']),
...mapGetters(['total']),
},
methods: {
...mapActions(['addProduct']),
toJSON() {
this.json = JSON.stringify({
products: this.products,
total: this.total,
}, null, '\t');
},
},
};
</script>
产品行
<template>
<tr>
<td colspan="2" class="id">{{indexFrom1}}</td>
<Editable v-model="item.name"/>
<Editable v-model="item.size"/>
<Editable v-model="item.content"/>
<Editable v-model.number="item.price"/>
<Editable v-model.number="item.number"/>
<Editable v-model="item.discount"/>
<td>
<select v-model="item.promotion">
<option selected="" value="0">Нет</option>
<optgroup label="Новоселы">
<option data-text="Нов." value="5">Новоселы -5%</option>
<option data-text="Нов." value="10">Новоселы -10%</option>
<option data-text="Нов." value="15">Новоселы -15%</option>
</optgroup>
</select>
</td>
<td>{{sum}}</td>
<Editable v-model.number="item.guarantee"/>
<td>
<select v-model="item.location">
<option selected value="">Услуги</option>
<option value="СКЛАД">Склад</option>
<option value="ЗАКАЗ">Заказ</option>
</select>
</td>
<td>
<span class="table-remove" @click="removeProduct(index)">Удалить</span>
</td>
</tr>
</template>
<script>
import Editable from './EditableCell';
export default {
components: {
Editable,
},
name: 'ProductRow',
props: {`enter code here`
initialItem: Object,
index: Number,
},
data() {
return {
item: this.initialItem
};
},
computed: {
sum() {
let prod = this.item.price * this.item.number;
let discounted = this.isDiscountInPercent(this.item.discount) ?
prod * this.getCoeffFromPercent(this.item.discount) :
prod - this.item.discount;
let result = Math.round(discounted * this.getCoeffFromPercent(this.item.promotion));
return result > 0 ? result : 0;
},
indexFrom1() {
return this.index + 1;
},
},
methods: {
getCoeffFromPercent(percent) {
return 1 - parseInt(percent) / 100;
},
isDiscountInPercent(discount) {
return ~discount.indexOf('%') ? true : false;
},
removeProduct(index) {
// console.log(arguments);
this.$store.dispatch('deleteProduct', index)
}
},
watch: {
sum() {
this.item.sum = this.sum;
},
item: {
handler(value) {
this.$store.dispatch('updateProduct', {
product: value,
index: this.index,
});
},
deep: true,
},
},
};
</script>
最佳答案
不,data()
不会创建项目对象的副本,因此在此代码中您将通过引用传递对象。
data() {
return {
item: this.initialItem
};
}
这意味着您商店中的产品对象与 ProductRow 组件中的 this.item
对象完全相同。因此,当您将 v-model
附加到输入时,您将直接更改商店中的产品对象。
在商店中使用 Object.assign()
克隆产品对象将不起作用。您必须在 ProductRow 组件中进行克隆。
data() {
return {
item: Object.assign({}, this.initialItem)
};
}
这将创建一个副本,以便您不会直接修改商店中的产品。
关于vue.js - 为什么 v-model 会改变 Vuex 状态而不是组件的本地数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50717471/