Digamos que tienes una tienda como esta. Y un componente donde selecciona un car de la lista que se pasa como accesorio a otro componente donde puede editar varias propiedades del objeto del car .
Si los objetos del automóvil tuvieran más propiedades como brand , weight , etc., tendría que:
Cree una nueva mutación para cada propiedad Esto tiene la desventaja de causar una gran cantidad de mutaciones, lo que hará que el archivo de la tienda sea muy ilegible en mi opinión.
Cree una mutación genérica que tome un objeto, un campo y un valor y asígnelos. Esto tiene la ventaja de tener solo una mutación genérica. Pero tiene la desventaja de que no puede manejar objetos anidados.
Cree un nuevo objeto de car en el componente y reemplácelo en el campo estado por id . Esto se siente como una solución limpia, pero implicaría copiar el objeto para eliminar la mutabilidad, y eso se siente menos limpio. Y también ralentizar la aplicación si la uso mucho, supongo.
En mis proyectos, terminé con una combinación de estas opciones que se siente caótica.
Entonces, mi pregunta es si hay un mejor patrón para usar que cualquiera de estos. Y si no, cuál se considera la mejor práctica. He buscado una respuesta a esto durante mucho tiempo, pero nunca encontré una buena explicación.
state: { cars: [ {id: 1, color: "blue"}, {id: 2, color: "blue"}, {id: 3, color: "blue"}, ] }, mutations: { setColor(state, {car, color}){ car.color = color; } }, getters: { allCars(state){ return state.cars; } },Lo haría usando la función deepMerge :
export const mutations = { setCarProperties(state, car) { const index = state.cars.findIndex((item) => item.id === car.id); Vue.set(state.cars, index, deepMerge(state.cars[index], car)); } };Para que pueda cambiar y agregar propiedades (incluso anidadas):
updateCarType() { const car = { id: 2, type: { engine: "electric", }, }; this.$store.commit("setCarProperties", car); },Tenga en cuenta que estoy usando Vue.set en mutación. Quizás se pregunte por qué no cambio de estado como lo hacemos normalmente:
state.cars[index] = deepMerge(state.cars[index], car));Pero eso no será reactivo, por lo que se necesita Vue.set. https://vuejs.org/v2/api/#Vue-set
Ver demostración: https://codesandbox.io/s/questions-69864025-vuex-update-property-of-object-in-array-causes-many-mutation-methods-sq09z?file=/pages/index.vue
Voy a compartir mis puntos de vista sobre este tema. Sinceramente, no conozco la mejor práctica, y hay un puñado de variaciones del mismo enfoque.
Básicamente, aplica solo el segundo principio que mencionaste y mantiene la reactividad durante todo el ciclo de actualización.
En la tienda haz una mutación genérica:
mutations: { setItem(state, item) { let idx = state.items.findIndex((i) => i.id === item.id); Vue.set(state.items, idx, item); } }En el componente, asigne los elementos de estado a una propiedad calculada y cree otro para el elemento editado:
computed: { ...mapGetters({ allItems: "allItems" }), localItem: { get() { return this.allItems[0]; }, set(val) { this.setItem(val); }, }, }, methods: { ...mapMutations(["setItem"]) }Y la parte complicada, actualiza el elemento local:
methods: { ...mapMutations(["setItem"]), changeItem() { this.$set(this, "localItem", { ...this.localItem, color: "red" }); }, }No actualice directamente las propiedades del objeto, sino que asigne el objeto completo, de modo que se active el definidor calculado y los cambios se confirmen para almacenar, sin ninguna advertencia.
Puede pasar el objeto del automóvil completo, con propiedades modificadas, a la acción:
async carUpdate({ commit }, car) { await // some axios or fetch call... commit('updateCar', car); },luego comete una mutación como:
updateCar(state, car) { state.cars = [ ...state.cars.map((item) => item.id !== car.id ? item : { ...item, ...car, } ), ]; },