@vadimek
Полуджун

Почему выходит ошибка во Vuex с v-model?

Делаю свой первый проект пробный проект на Vue (переписываю существующее с Jquery). Сначала сделал все просто без Vuex, потом добавил оный (дальше пригодится и интереса ради). Все вроде бы нормально, но включил strict mode и получил ошибку: изменения storage вне мутации, хотя этого не замечал. Пытаюсь разобраться.

Как я понял, v-model в компоненте ProductRow обновляет почему-то не локальную копию item, а state у стора, причем начиная только со второго срабатывания события input. В целом я смог исправить, но хочу понять почему этот вариант не работает.

Вот код:
storage.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';

Vue.use(Vuex);

let id = 1;
const product = {
    id: 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,
            prepaymentInRub: 0,
        }

    },
    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 Math.round(getters.total - getters.prepaymentInRub);
        },
        prepaymentInRub(state, getters) {
            return Math.round(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.id = id++;
            commit(ADD_PRODUCT, newProduct);
        },
        updateProduct({commit}, product) {
            commit(UPDATE_PRODUCT, product);
        },
        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.id"
                    :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>

ProductRow.vue
<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>
                <option value="Выст. образец из М1">Выставочный образец из 1 магазина</option>
                <option value="Выст. образец из М12">Выставочный образец из 12 магазина</option>
                <option value="Выст. образец из М14">Выставочный образец из 14 магазина</option>
                <option value="Выст. образец из М16">Выставочный образец из 16 магазина</option>
                <option value="Выст. образец из М17">Выставочный образец из 17 магазина</option>
                <option value="Выст. образец из М18">Выставочный образец из 18 магазина</option>
                <option value="Выст. образец из М19">Выставочный образец из 19 магазина</option>
                <option value="Выст. образец из М20">Выставочный образец из 20 магазина</option>
                <option value="Выст. образец из М21">Выставочный образец из 21 магазина</option>
                <option value="Выст. образец из М22">Выставочный образец из 22 магазина</option>
            </select>
        </td>
        <td>
            <span class="table-remove" @click="removeProduct">Удалить</span>
        </td>
    </tr>
</template>

<script>
    import Editable from './EditableCell';

    export default {
        components: {
            Editable,
        },
        name: 'ProductRow',
        props: {
            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() {
                this.$store.dispatch('deleteProduct', this.index)
            },
        },
        watch: {
            sum() {
                this.item.sum = this.sum;
            },
            item: {
                handler(value) {
                    this.$store.dispatch('updateProduct', {
                        product: value,
                        index: this.index,
                    });
                },
                deep: true,
            },
        },
    };
</script>
  • Вопрос задан
  • 630 просмотров
Решения вопроса 1
0xD34F
@0xD34F Куратор тега Vue.js
Как я понял, v-model в компоненте ProductRow обновляет почему-то не локальную копию item, а state у стора

А с чего вы взяли, что у вас тут где-то есть какая-то "локальная копия"? Нет, серьёзно - попробуйте показать то место у себя в коде, где, как вам кажется, вы создаёте копии значений из стора.

Предположу, что...
...под копированием вы подразумеваете

data() {
    return {
        'item': this.initialItem
    }
}

Нет, это не копирование объекта - так вы лишь создали ещё одну ссылку на объект. Для создания копии данных делайте так: Object.assign({}, this.initialItem). Или так: { ...this.initialItem }.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы