Vue 2 - Puntelli mutanti vue-warn


181

Ho iniziato https://laracasts.com/series/learning-vue-step-by-step series. Mi sono fermato sulla lezione Vue, Laravel e AJAX con questo errore:

vue.js: 2574 [Vue warn]: evita di mutare direttamente un oggetto di scena poiché il valore verrà sovrascritto ogni volta che il componente genitore ritorna. Invece, usa un dato o una proprietà calcolata in base al valore dell'elica. Prop muting: "list" (trovato nel componente)

Ho questo codice in main.js

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

So che il problema è in create () quando sovrascrivo la lista prop, ma sono un principiante in Vue, quindi non so assolutamente come risolverlo. Qualcuno ha idea di come (e per favore spiega perché) risolverlo?


2
Indovina, è solo un messaggio di avvertimento e non un errore.
David R,

Risposte:


264

Ciò ha a che fare con il fatto che la mutazione di un puntello a livello locale è considerata un anti-pattern in Vue 2

Quello che dovresti fare ora, nel caso tu voglia mutare localmente un oggetto , è dichiarare un campo nel tuo datache usa il propsvalore come valore iniziale e quindi mutare la copia:

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

Puoi leggere di più al riguardo sulla guida ufficiale di Vue.js


Nota 1: noti prego che si dovrebbe non utilizzare lo stesso nome per il propedata , vale a dire:

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

Nota 2: poiché ritengo che ci sia un po 'di confusione riguardo propse reattività , ti suggerisco di dare un'occhiata a questo thread


3
Questa è un'ottima risposta, ma penso anche che dovrebbe essere aggiornato per includere l'associazione di eventi. Un evento attivato dal figlio può aggiornare il genitore, che passerà i sostegni aggiornati al figlio. In questo modo, la fonte della verità è ancora mantenuta in tutti i componenti. Potrebbe anche valere la pena menzionare Vuex per la gestione dello stato condiviso tra più componenti.
Wes Harper,

@WesHarper, è anche possibile utilizzare il modificatore .sync come scorciatoia per ottenere automaticamente l'evento di aggiornamento del genitore.
danb4r

48

Il modello Vue è propssu e giù events. Sembra semplice, ma è facile dimenticare quando si scrive un componente personalizzato.

A partire da Vue 2.2.0 è possibile utilizzare v-model (con proprietà calcolate ). Ho scoperto che questa combinazione crea un'interfaccia semplice, pulita e coerente tra i componenti:

  • Qualsiasi propspassaggio al componente rimane reattivo (ovvero non viene clonato né richiede una watchfunzione per aggiornare una copia locale quando vengono rilevate modifiche).
  • Le modifiche vengono automaticamente emesse al genitore.
  • Può essere utilizzato con più livelli di componenti.

Una proprietà calcolata consente di definire separatamente setter e getter. Ciò consente Taskdi riscrivere il componente come segue:

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 proprietà del modello definisce a quale propè associato v-modele quale evento verrà emesso sulle modifiche. È quindi possibile chiamare questo componente dal genitore come segue:

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

La listLocalproprietà calcolata fornisce una semplice interfaccia getter e setter all'interno del componente (pensala come una variabile privata). All'interno #task-templateè possibile eseguire il rendering listLocale questo rimarrà reattivo (ovvero, se viene parentListmodificato, aggiornerà il Taskcomponente). Puoi anche mutare listLocalchiamando il setter (ad esempio, this.listLocal = newList) ed emetterà la modifica al genitore.

La cosa grandiosa di questo modello è che puoi passare listLocala un componente figlio di Task(usando v-model), e le modifiche dal componente figlio si propagheranno al componente di livello superiore.

Ad esempio, supponiamo di avere un EditTaskcomponente separato per apportare qualche tipo di modifica ai dati dell'attività. Usando lo stesso v-modelmodello di proprietà calcolato e possiamo passare listLocalal componente (usando v-model):

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

Se EditTaskemette una modifica, verrà richiamato set()in listLocalmodo appropriato e quindi propagherà l'evento al livello superiore. Allo stesso modo, il EditTaskcomponente potrebbe anche chiamare altri componenti figlio (come gli elementi del modulo) utilizzando v-model.


1
Sto facendo qualcosa di simile tranne che con la sincronizzazione. Il problema è che devo eseguire un altro metodo dopo aver emesso il mio evento change, tuttavia quando il metodo viene eseguito e raggiunge il getter, ottengo il vecchio valore perché l'evento change non è stato ancora raccolto dall'ascoltatore / propagato al figlio. Questo è un caso in cui voglio mutare l'elica in modo che i miei dati locali siano corretti fino a quando l'aggiornamento principale non si propaga nuovamente. Qualche idea al riguardo?
koga73,

Ho il sospetto che chiamare il getter dal setter non sia una buona pratica. Quello che potresti considerare è mettere un orologio sulla tua proprietà calcolata e aggiungere la tua logica lì.
chris,

puntelli in basso ed eventi in alto è ciò che mi ha fatto clic. Dove è spiegato nei documenti di VueJs?
nebulousGirl


Penso che questo sia il modo più indolore di emettere le modifiche al componente genitore.
Muhammad,

36

Vue ti avverte solo: cambi il puntello nel componente, ma quando il componente genitore ritorna, "list" verrà sovrascritto e perderai tutte le modifiche. Quindi è pericoloso farlo.

Utilizzare invece la proprietà calcolata in questo modo:

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

25
Che ne dite di un puntello a 2 vie?
Josh R.,

7
Se vuoi l'associazione dati a 2 vie, dovresti usare eventi personalizzati, per maggiori informazioni leggi: vuejs.org/v2/guide/components.html#sync-Modifier Non puoi ancora modificare direttamente il prop, hai bisogno di un evento e una funzione che gestisce un cambio di prop per te.
Marco,

22

Se stai usando Lodash, puoi clonare l'elica prima di restituirla. Questo modello è utile se si modifica tale prop sul padre e sul figlio.

Diciamo che abbiamo un elenco di oggetti sulla griglia dei componenti .

Nel componente padre

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

Nel componente figlio

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

19

Puntelli in giù, eventi in alto. Questo è il modello di Vue. Il punto è che se provi a mutare oggetti di scena che passano da un genitore. Non funzionerà e verrà sovrascritto ripetutamente dal componente genitore. Il componente figlio può solo emettere un evento per avvisare il componente padre di fare sth. Se non ti piacciono questi limiti, puoi usare VUEX (in realtà questo schema risucchia nella struttura di componenti complessi, dovresti usare VUEX!)


2
C'è anche un'opzione per usare il bus degli eventi
Steven Pribilinskiy il

il bus degli eventi non è supportato nativamente in vue 3. github.com/vuejs/rfcs/pull/118
AlexMA

15

Non dovresti cambiare il valore degli oggetti di scena nel componente figlio. Se hai davvero bisogno di cambiarlo puoi usare .sync. Proprio come questo

<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

Secondo VueJs 2.0, non è necessario mutare un oggetto all'interno del componente. Sono solo mutati dai loro genitori. Pertanto, è necessario definire le variabili nei dati con nomi diversi e tenerle aggiornate osservando gli oggetti di scena reali. Nel caso in cui il prop di lista sia cambiato da un genitore, puoi analizzarlo e assegnarlo a mutableList. Ecco una soluzione 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);
        }
    }
});

Utilizza mutableList per rendere il tuo modello, quindi mantieni sicuro il tuo elenco di oggetti nel componente.


l'orologio non è costoso da usare?
Calcia Buttowski

6

non cambiare gli oggetti di scena direttamente nei componenti. se è necessario cambiarli impostare una nuova proprietà come questa:

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

E cambia il valore di listClone.


6

La risposta è semplice, dovresti interrompere la mutazione diretta prop assegnando il valore ad alcune variabili del componente locale (potrebbe essere proprietà dei dati, calcolata con getter, setter o watcher).

Ecco una soluzione semplice che utilizza l'osservatore.

<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>

È quello che uso per creare qualsiasi componente di input di dati e funziona perfettamente. Qualsiasi nuovo dato inviato (v-model (ed)) dal genitore verrà guardato dal watcher del valore e assegnato alla variabile di input e una volta ricevuto l'input, possiamo catturare quell'azione ed emettere l'input al genitore suggerendo che i dati sono immessi dall'elemento forma.


5

Ho affrontato anche questo problema. L'avvertimento è andato dopo che uso $one $emit. È qualcosa di simile all'uso $one $emitconsigliato di inviare dati dal componente figlio al componente padre.


4

Se vuoi mutare oggetti di scena, usa l'oggetto.

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

componente:

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

Proprio come una nota, devi stare attento quando muti gli oggetti. Gli oggetti Javascript vengono passati per riferimento, ma esiste un avvertimento: il riferimento viene interrotto quando si imposta una variabile uguale a un valore.
ira,

3

Devi aggiungere un metodo calcolato come questo

component.vue

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

3

Flusso di dati unidirezionale, secondo https://vuejs.org/v2/guide/components.html , il componente segue il flusso di dati unidirezionale, tutti i puntelli formano un'associazione unidirezionale tra la proprietà figlio e il genitore uno, quando la proprietà del genitore si aggiorna, passerà al figlio ma non viceversa, questo impedisce ai componenti del figlio di mutare accidentalmente il genitore, il che può rendere più difficile la comprensione del flusso di dati della tua app.

Inoltre, ogni volta che il componente padre viene aggiornato, tutti gli oggetti di scena nei componenti figlio verranno aggiornati con l'ultimo valore. Ciò significa che non dovresti tentare di mutare un oggetto all'interno di un componente figlio. Se lo fai .vue ti avvertirà nella console.

Di solito ci sono due casi in cui si è tentati di mutare un prop: il prop è usato per passare un valore iniziale; il componente figlio desidera successivamente utilizzarlo come proprietà di dati locali. L'elica viene trasmessa come valore grezzo che deve essere trasformato. La risposta corretta a questi casi d'uso è: Definire una proprietà di dati locali che utilizza il valore iniziale del prop come valore iniziale:

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

Definire una proprietà calcolata calcolata dal valore dell'elica:

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

2

Gli oggetti di scena Vue.js non devono essere mutati poiché questo è considerato un Anti-Pattern in Vue.

L'approccio che dovrai adottare è la creazione di una proprietà di dati sul componente che fa riferimento alla proprietà prop originale dell'elenco

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

Ora la struttura dell'elenco che viene passata al componente viene referenziata e modificata tramite la dataproprietà del componente :-)

Se desideri fare di più che analizzare la proprietà della tua lista, usa la computedproprietà del componente Vue ' . Ciò ti consente di apportare mutazioni più approfondite ai tuoi oggetti di scena.

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

L'esempio sopra analizza il tuo elenco di oggetti e lo filtra solo per gli elenchi attivi , lo disconnette per schnitts e risatine e lo restituisce.

nota : entrambe le proprietà datae computedsono indicate nello stesso modello, ad es

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

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

Può essere facile pensare che una computedproprietà (essendo un metodo) debba essere chiamata ... non è così


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'
})

Forse questo soddisferà le tue esigenze.


2

Aggiungendo alla migliore risposta,

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

L'impostazione degli oggetti di scena di un array è pensata per lo sviluppo / prototipazione, in produzione assicurarsi di impostare i tipi di oggetti ( https://vuejs.org/v2/guide/components-props.html ) e impostare un valore predefinito nel caso in cui l'elica non abbia stato popolato dal genitore, in questo modo.

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

In questo modo potresti ottenere un oggetto vuoto mutableListinvece di un errore JSON.parse se non è definito.


1

Di seguito è riportato un componente snack bar, quando do la variabile snackbar direttamente nel modello v in questo modo se funzionerà ma nella console, verrà visualizzato un errore come

Evita di mutare direttamente un oggetto di scena poiché il valore verrà sovrascritto ogni volta che il componente genitore esegue nuovamente il rendering. Invece, usa un dato o una proprietà calcolata in base al valore dell'elica.

<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>

Il modo corretto per sbarazzarsi di questo errore di mutazione è usare watcher

<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>

Quindi nel componente principale in cui caricherai questo snack bar puoi semplicemente fare questo codice

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

Questo ha funzionato per me


0

Vue.js lo considera un anti-pattern. Ad esempio, dichiarando e impostando alcuni oggetti di scena come

this.propsVal = 'new Props Value'

Quindi per risolvere questo problema devi prendere un valore dagli oggetti di scena ai dati o alla proprietà calcolata di un'istanza Vue, in questo modo:

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

Funzionerà sicuramente.


0

Oltre a quanto sopra, per gli altri che hanno il seguente problema:

"Se il valore degli oggetti di scena non è richiesto e quindi non viene sempre restituito, i dati passati verranno restituiti undefined(anziché vuoti)". Che potrebbe rovinare <select>il valore predefinito, l'ho risolto controllando se il valore è impostato beforeMount()(e impostato in caso contrario) come segue:

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

Voglio dare questa risposta che aiuta a evitare di usare molto codice, watcher e proprietà calcolate. In alcuni casi questa può essere una buona soluzione:

I puntelli sono progettati per fornire comunicazioni unidirezionali.

Quando hai un show/hidepulsante modale con un sostegno, la soluzione migliore per me è emettere un evento:

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

Quindi aggiungi listener all'elemento modale:

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

(In questo caso l'elica non showè probabilmente necessaria perché puoi usare un facile v-if="show"direttamente sul modale di base)


0

Personalmente suggerisco sempre se hai bisogno di mutare gli oggetti di scena, prima di passarli alla proprietà calcolata e tornare da lì, in seguito uno può mutare facilmente gli oggetti di scena, anche se puoi monitorare la mutazione dell'elica, se quelli sono mutati da un altro componente o possiamo anche guardare.


0

Poiché Vue props è un flusso di dati a senso unico, questo impedisce ai componenti figlio di mutare accidentalmente lo stato del genitore.

Dal documento ufficiale di Vue, troveremo 2 modi per risolvere questo problema

  1. se il componente figlio vuole usare oggetti di scena come dati locali, è meglio definire una proprietà di dati locali.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. L'elica viene trasmessa come valore grezzo che deve essere trasformato. In questo caso, è meglio definire una proprietà calcolata usando il valore dell'elica:

      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Ì !, gli attributi mutanti in vue2 sono anti-pattern. MA ... Basta infrangere le regole usando altre regole e andare avanti! Ciò di cui hai bisogno è l'aggiunta del modificatore .sync all'attributo del componente nell'ambito del paret. <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡 È semplice uccidere il batman 😁


0

Per quando TypeScript è il tuo lang preferito. di sviluppo

<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;
}

e non

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



@Prop() fees: any = 0;
get feesInLocale() {
    return this.fees;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.