Vue 2: accesorios de mutación vue-warn


180

Empecé https://laracasts.com/series/learning-vue-step-by-step series. Paré en la lección Vue, Laravel y AJAX con este error:

vue.js: 2574 [Vue warn]: evite mutar un accesorio directamente ya que el valor se sobrescribirá cada vez que el componente principal se vuelva a representar. En su lugar, use una propiedad de datos o calculada basada en el valor del accesorio. Prop siendo mutado: "lista" (encontrado en componente)

Tengo este código en main.js

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    created() {
        this.list = JSON.parse(this.list);
    }
});
new Vue({
    el: '.container'
})

Sé que el problema está en created () cuando sobrescribo la lista de accesorios, pero soy un novato en Vue, así que no sé cómo solucionarlo. ¿Alguien tiene una idea de cómo (y explique por qué) solucionarlo?


2
Supongo que es solo un mensaje de advertencia y no un error.
David R

Respuestas:


264

Esto tiene que ver con el hecho de que mutar un accesorio localmente se considera un antipatrón en Vue 2

Lo que debe hacer ahora, en caso de que quiera mutar un accesorio localmente , es declarar un campo en su dataque use el propsvalor como su valor inicial y luego mutar la copia:

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

Puedes leer más sobre esto en la guía oficial de Vue.js


Nota 1: Nota Por favor, que usted debe no utilizar el mismo nombre para su propedata , es decir:

data: function () { return { list: JSON.parse(this.list) } // WRONG!!

Nota 2: Desde Siento que hay cierta confusión respecto propsy reactividad , lo que sugeriría echar un vistazo a este hilo


3
Esta es una gran respuesta, pero también creo que debería actualizarse para incluir el enlace de eventos. Un evento desencadenado desde el elemento secundario puede actualizar al elemento primario, que pasará los elementos actualizados al elemento secundario. De esa manera, la fuente de la verdad todavía se mantiene en todos los componentes. También vale la pena mencionar a Vuex para administrar el estado que se comparte entre múltiples componentes.
Wes Harper

@WesHarper, también es posible usar el modificador .sync como una forma abreviada para obtener automáticamente el evento de actualización de los padres.
danb4r

48

El patrón Vue es propshacia abajo y hacia eventsarriba. Suena simple, pero es fácil de olvidar al escribir un componente personalizado.

A partir de Vue 2.2.0, puede usar v-model (con propiedades calculadas ). He descubierto que esta combinación crea una interfaz simple, limpia y consistente entre componentes:

  • Todo lo que se propspasa a su componente permanece reactivo (es decir, no está clonado ni requiere una watchfunción para actualizar una copia local cuando se detectan cambios).
  • Los cambios se emiten automáticamente a los padres.
  • Se puede usar con múltiples niveles de componentes.

Una propiedad calculada permite que el definidor y el captador se definan por separado. Esto permite que el Taskcomponente se reescriba de la siguiente manera:

Vue.component('Task', {
    template: '#task-template',
    props: ['list'],
    model: {
        prop: 'list',
        event: 'listchange'
    },
    computed: {
        listLocal: {
            get: function() {
                return this.list
            },
            set: function(value) {
                this.$emit('listchange', value)
            }
        }
    }
})  

La propiedad del modelo define qué propestá asociado v-modely qué evento se emitirá en los cambios. Luego puede llamar a este componente desde el padre de la siguiente manera:

<Task v-model="parentList"></Task>

La listLocalpropiedad calculada proporciona una interfaz getter y setter simple dentro del componente (piense que es una variable privada). Dentro de #task-templateusted puede renderizar listLocaly permanecerá reactivo (es decir, si parentListcambia, actualizará el Taskcomponente). También puede mutar listLocalllamando al configurador (por ejemplo, this.listLocal = newList) y emitirá el cambio al padre.

Lo bueno de este patrón es que puede pasar listLocala un componente secundario de Task(usar v-model), y los cambios del componente secundario se propagarán al componente de nivel superior.

Por ejemplo, supongamos que tenemos un EditTaskcomponente separado para realizar algún tipo de modificación en los datos de la tarea. Al usar el mismo v-modelpatrón de propiedades calculadas podemos pasar listLocalal componente (usando v-model):

<script type="text/x-template" id="task-template">
    <div>
        <EditTask v-model="listLocal"></EditTask>
    </div>
</script>

Si EditTaskemite un cambio llamará apropiadamente set()en listLocaly por lo tanto propagar el evento para el nivel superior. Del mismo modo, el EditTaskcomponente también podría llamar a otros componentes secundarios (como elementos de formulario) usando v-model.


1
Estoy haciendo algo similar excepto con sincronización. El problema es que necesito ejecutar otro método después de emitir mi evento de cambio, sin embargo, cuando el método se ejecuta y alcanza el getter, obtengo el valor anterior porque el evento de cambio aún no ha sido recogido por el oyente / propagado de nuevo al niño. Este es un caso en el que quiero mutar el accesorio para que mis datos locales sean correctos hasta que la actualización principal se propague nuevamente. ¿Alguna idea al respecto?
koga73

Sospecho que llamar al getter desde el setter no es una buena práctica. Lo que podría considerar es vigilar su propiedad calculada y agregar su lógica allí.
Chris

apuntala hacia abajo y los eventos hacia arriba es lo que hizo clic conmigo. ¿Dónde se explica esto en los documentos de VueJs?
nebulousGirl


Creo que esta es la forma más indolora de emitir los cambios en el componente principal.
Muhammad

36

Vue simplemente te advierte: cambias la utilería en el componente, pero cuando el componente padre se vuelve a representar, se sobrescribirá la "lista" y perderás todos los cambios. Entonces es peligroso hacerlo.

Utilice la propiedad calculada en su lugar de esta manera:

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
        listJson: function(){
            return JSON.parse(this.list);
        }
    }
});

25
¿Qué pasa con un accesorio de unión de 2 vías?
Josh R.

77
Si desea un enlace de datos bidireccional, debe usar eventos personalizados; para obtener más información, lea: vuejs.org/v2/guide/components.html#sync-Modifier Aún no puede modificar directamente el accesorio, necesita un evento y una función que maneja un cambio de utilería por ti.
Marco

22

Si está usando Lodash, puede clonar el accesorio antes de devolverlo. Este patrón es útil si modifica ese accesorio tanto en el padre como en el hijo.

Digamos que tenemos una lista de accesorios en la cuadrícula de componentes .

En componente principal

<grid :list.sync="list"></grid>

En componente infantil

props: ['list'],
methods:{
    doSomethingOnClick(entry){
        let modifiedList = _.clone(this.list)
        modifiedList = _.uniq(modifiedList) // Removes duplicates
        this.$emit('update:list', modifiedList)
    }
}

19

Apoyos, eventos arriba. Ese es el patrón de Vue. El punto es que si intentas mutar los accesorios que pasan de un padre. No funcionará y el componente principal lo sobrescribe repetidamente. El componente hijo solo puede emitir un evento para notificar al componente padre que haga algo. Si no le gustan estas restricciones, puede usar VUEX (en realidad, este patrón absorberá la estructura de componentes complejos, ¡debe usar VUEX!)


2
También hay una opción para usar el bus de eventos
Steven Pribilinskiy

El bus de eventos no se admite de forma nativa en vue 3. github.com/vuejs/rfcs/pull/118
AlexMA

15

No debe cambiar el valor de los accesorios en el componente hijo. Si realmente necesita cambiarlo, puede usarlo .sync. Solo asi

<your-component :list.sync="list"></your-component>

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    created() {
        this.$emit('update:list', JSON.parse(this.list))
    }
});
new Vue({
    el: '.container'
})

7

De acuerdo con VueJs 2.0, no debe mutar un accesorio dentro del componente. Solo son mutados por sus padres. Por lo tanto, debe definir variables en los datos con diferentes nombres y mantenerlas actualizadas viendo accesorios reales. En caso de que un padre cambie el accesorio de la lista, puede analizarlo y asignarlo a mutableList. Aquí hay una solución completa.

Vue.component('task', {
    template: ´<ul>
                  <li v-for="item in mutableList">
                      {{item.name}}
                  </li>
              </ul>´,
    props: ['list'],
    data: function () {
        return {
            mutableList = JSON.parse(this.list);
        }
    },
    watch:{
        list: function(){
            this.mutableList = JSON.parse(this.list);
        }
    }
});

Utiliza mutableList para representar su plantilla, por lo tanto, mantiene su accesorio de lista seguro en el componente.


¿No es costoso ver el reloj?
Kick Buttowski

6

no cambie los accesorios directamente en los componentes. Si necesita cambiarlo, establezca una nueva propiedad como esta:

data () {
    return () {
        listClone: this.list
    }
}

Y cambie el valor de listClone.


6

La respuesta es simple: debe romper la mutación de apoyo directo asignando el valor a algunas variables de componentes locales (podrían ser propiedades de datos, calculadas con captadores, establecedores o observadores).

Aquí hay una solución simple usando el observador.

<template>
  <input
    v-model="input"
    @input="updateInput" 
    @change="updateInput"
  />

</template>

<script>
  export default {
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      input: '',
    };
  },
  watch: {
    value: {
      handler(after) {
        this.input = after;
      },
      immediate: true,
    },
  },
  methods: {
    updateInput() {
      this.$emit('input', this.input);
    },
  },
};
</script>

Es lo que uso para crear cualquier componente de entrada de datos y funciona bien. Cualquier nuevo dato enviado (v-model (ed)) desde el padre será observado por el observador de valores y se asignará a la variable de entrada y una vez que se reciba la entrada, podemos capturar esa acción y emitir entrada al padre sugiriendo que los datos son ingresados del elemento de forma.


5

También enfrenté este problema. La advertencia desapareció después de usar $ony $emit. Es algo así como el uso $ony se $emitrecomienda enviar datos del componente secundario al componente primario.


4

Si quieres mutar los accesorios, usa object.

<component :model="global.price"></component>

componente:

props: ['model'],
methods: {
  changeValue: function() {
    this.model.value = "new value";
  }
}

Solo como una nota, debes tener cuidado al mutar objetos. Los objetos Javascript se pasan por referencia, pero hay una advertencia: la referencia se rompe cuando establece una variable igual a un valor.
ira

3

Necesita agregar un método calculado como este

componente.vue

props: ['list'],
computed: {
    listJson: function(){
        return JSON.parse(this.list);
    }
}

3

Flujo de datos unidireccional, de acuerdo con https://vuejs.org/v2/guide/components.html , el componente sigue el flujo de datos unidireccional , todos los accesorios forman un enlace unidireccional entre la propiedad secundaria y el padre uno, cuando la propiedad padre se actualiza, fluirá hacia el niño pero no al revés, esto evita que los componentes hijos muten accidentalmente el padre, lo que puede dificultar la comprensión de los datos de su aplicación.

Además, cada vez que se actualiza el componente principal, todos los accesorios en los componentes secundarios se actualizarán con el último valor. Esto significa que no debe intentar mutar un accesorio dentro de un componente secundario. Si lo haces .vue te avisará en la consola.

Por lo general, hay dos casos en los que es tentador mutar un accesorio: el accesorio se usa para pasar un valor inicial; el componente hijo quiere usarlo luego como una propiedad de datos local. El accesorio se pasa como un valor bruto que necesita ser transformado. La respuesta adecuada a estos casos de uso es: Defina una propiedad de datos local que use el valor inicial del accesorio como su valor inicial:

props: ['initialCounter'],
data: function () {
  return { counter: this.initialCounter }
}

Defina una propiedad calculada que se calcule a partir del valor del accesorio:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

2

Los accesorios de Vue.js no deben mutarse, ya que esto se considera un Anti-Pattern en Vue.

El enfoque que tendrá que tomar es la creación de una propiedad de datos en el componente que hace referencia el original prop propiedad de la lista

props: ['list'],
data: () {
  return {
    parsedList: JSON.parse(this.list)
  }
}

Ahora su estructura de lista que se pasa al componente se referencia y muta a través de la datapropiedad de su componente :-)

Si desea hacer algo más que analizar su propiedad de lista, utilice la computedpropiedad del componente Vue . Esto le permite hacer mutaciones más profundas en sus accesorios.

props: ['list'],
computed: {
  filteredJSONList: () => {
    let parsedList = JSON.parse(this.list)
    let filteredList = parsedList.filter(listItem => listItem.active)
    console.log(filteredList)
    return filteredList
  }
}

El ejemplo anterior analiza su accesorio de lista y lo filtra a solo elementos de lista activos , lo cierra para detectar schnitts y risitas y lo devuelve.

nota : ambas propiedades datay computedestán referenciadas en la plantilla de la misma manera, por ejemplo

<pre>{{parsedList}}</pre>

<pre>{{filteredJSONList}}</pre>

Puede ser fácil pensar que una computedpropiedad (siendo un método) necesita ser llamada ... no


2
Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
      middleData() {
        return this.list
      }
    },
    watch: {
      list(newVal, oldVal) {
        console.log(newVal)
        this.newList = newVal
      }
    },
    data() {
      return {
        newList: {}
      }
    }
});
new Vue({
    el: '.container'
})

Quizás esto satisfaga sus necesidades.


2

Agregando a la mejor respuesta,

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

La configuración de accesorios por una matriz está destinada a desarrollo / creación de prototipos, en producción, asegúrese de establecer los tipos de accesorios ( https://vuejs.org/v2/guide/components-props.html ) y establecer un valor predeterminado en caso de que el accesorio no haya sido poblado por el padre, como tal.

Vue.component('task', {
    template: '#task-template',
    props: {
      list: {
        type: String,
        default() {
          return '{}'
        }
      }
    },
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

De esta manera, al menos obtiene un objeto vacío en mutableListlugar de un error JSON.parse si no está definido.


0

Vue.js considera esto un antipatrón. Por ejemplo, declarando y configurando algunos accesorios como

this.propsVal = 'new Props Value'

Entonces, para resolver este problema, debe tomar un valor de los accesorios a los datos o la propiedad calculada de una instancia de Vue, como esta:

props: ['propsVal'],
data: function() {
   return {
       propVal: this.propsVal
   };
},
methods: {
...
}

Esto definitivamente funcionará.


0

Además de lo anterior, para otros que tengan el siguiente problema:

"Si el valor de los accesorios no es obligatorio y, por lo tanto, no siempre se devuelve, los datos pasados ​​volverían undefined(en lugar de estar vacíos)". Lo que podría alterar <select>el valor predeterminado, lo resolví comprobando si el valor está configurado beforeMount()(y configúrelo si no) de la siguiente manera:

JS:

export default {
        name: 'user_register',
        data: () => ({
            oldDobMonthMutated: this.oldDobMonth,
        }),
        props: [
            'oldDobMonth',
            'dobMonths', //Used for the select loop
        ],
        beforeMount() {
           if (!this.oldDobMonth) {
              this.oldDobMonthMutated = '';
           } else {
              this.oldDobMonthMutated = this.oldDobMonth
           }
        }
}

HTML:

<select v-model="oldDobMonthMutated" id="dob_months" name="dob_month">

 <option selected="selected" disabled="disabled" hidden="hidden" value="">
 Select Month
 </option>

 <option v-for="dobMonth in dobMonths"
  :key="dobMonth.dob_month_slug"
  :value="dobMonth.dob_month_slug">
  {{ dobMonth.dob_month_name }}
 </option>

</select>

0

Quiero dar esta respuesta que ayuda a evitar el uso de mucho código, observadores y propiedades calculadas. En algunos casos, esta puede ser una buena solución:

Los accesorios están diseñados para proporcionar comunicación unidireccional.

Cuando tienes un show/hidebotón modal con un accesorio, la mejor solución para mí es emitir un evento:

<button @click="$emit('close')">Close Modal</button>

Luego agregue oyente al elemento modal:

<modal :show="show" @close="show = false"></modal>

(En este caso, la utilería showprobablemente no sea necesaria porque puede usar una fácil v-if="show"directamente en el modal base)


0

Personalmente, siempre sugiero que si necesita mutar los accesorios, primero páselos a la propiedad calculada y regrese a partir de ahí, luego puede mutar los accesorios fácilmente, incluso si puede rastrear la mutación del accesorio, si están siendo mutados de otro componente también o podemos ver también.


0

Debido a que Vue props es un flujo de datos unidireccional, esto evita que los componentes secundarios muten accidentalmente el estado del padre.

Del documento oficial de Vue, encontraremos 2 formas de resolver este problema

  1. Si el componente hijo quiere usar accesorios como datos locales, es mejor definir una propiedad de datos local.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. El accesorio se pasa como un valor bruto que necesita ser transformado. En este caso, es mejor definir una propiedad calculada utilizando el valor del accesorio:

      props: ['list'],
      computed: {
        localList: function() {
           return JSON.parse(this.list);
        },
        //eg: if you want to filter this list
        validList: function() {
           return this.list.filter(product => product.isValid === true)
        }
        //...whatever to transform the list
      }
    
    

0

¡SÍ !, los atributos de mutación en vue2 son antipatrón. PERO ... ¡Solo rompa las reglas usando otras reglas y avance! Lo que necesita es agregar el modificador .sync a su atributo de componente en el ámbito de paret. <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡 Es simple, matamos al batman 😁


0

Para cuando TypeScript es su idioma preferido. de desarrollo

<template>
<span class="someClassName">
      {{feesInLocale}}
</span>
</template>  



@Prop({default: 0}) fees: any;

// computed are declared with get before a function
get feesInLocale() {
    return this.fees;
}

y no

<template>
<span class="someClassName">
      {{feesInLocale}}
</span>
</template>  



@Prop() fees: any = 0;
get feesInLocale() {
    return this.fees;
}

0

A continuación hay un componente de snack bar, cuando doy el snackbar variable directamente en v-model como este si funciona pero en la consola, dará un error como

Evite mutar un accesorio directamente ya que el valor se sobrescribirá cada vez que el componente principal se vuelva a representar. En su lugar, use una propiedad de datos o calculada basada en el valor del accesorio.

<template>
        <v-snackbar v-model="snackbar">
        {{ text }}
      </v-snackbar>
</template>

<script>
    export default {
        name: "loader",

        props: {
            snackbar: {type: Boolean, required: true},
            text: {type: String, required: false, default: ""},
        },

    }
</script>

La forma correcta de deshacerse de este error de mutación es usar vigilante

<template>
        <v-snackbar v-model="snackbarData">
        {{ text }}
      </v-snackbar>
</template>

<script>
/* eslint-disable */ 
    export default {
        name: "loader",
         data: () => ({
          snackbarData:false,
        }),
        props: {
            snackbar: {type: Boolean, required: true},
            text: {type: String, required: false, default: ""},
        },
        watch: { 
        snackbar: function(newVal, oldVal) { 
          this.snackbarData=!this.snackbarDatanewVal;
        }
      }
    }
</script>

Entonces, en el componente principal donde cargará este snack bar, simplemente puede hacer este código

 <loader :snackbar="snackbarFlag" :text="snackText"></loader>

Esto funcionó para mi

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.