Vue 2 - Mutierende Requisiten vue-warn


179

Ich habe die Serie https://laracasts.com/series/learning-vue-step-by-step gestartet . Ich habe die Lektion Vue, Laravel und AJAX mit diesem Fehler abgebrochen :

vue.js: 2574 [Vue warn]: Vermeiden Sie es, eine Requisite direkt zu mutieren, da der Wert bei jedem erneuten Rendern der übergeordneten Komponente überschrieben wird. Verwenden Sie stattdessen Daten oder berechnete Eigenschaften, die auf dem Wert der Requisite basieren. Prop mutiert: "Liste" (in Komponente gefunden)

Ich habe diesen Code in main.js

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

Ich weiß, dass das Problem in created () liegt, wenn ich die Listen-Requisite überschreibe, aber ich bin ein Neuling in Vue, daher weiß ich überhaupt nicht, wie ich es beheben soll. Hat jemand eine Idee, wie (und bitte erklären Sie warum), um das Problem zu beheben?


2
Ich denke, es ist nur eine Warnmeldung und kein Fehler.
David R

Antworten:


263

Dies hat damit zu tun, dass die lokale Mutation einer Requisite in Vue 2 als Anti-Pattern angesehen wird

Was Sie jetzt tun sollten, wenn Sie eine Requisite lokal mutieren möchten , ist, ein Feld in Ihrem zu deklarieren data, das den propsWert als Anfangswert verwendet, und dann die Kopie zu mutieren:

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

Weitere Informationen hierzu finden Sie im offiziellen Leitfaden von Vue.j.


Hinweis 1: Bitte beachten Sie, dass Sie nicht denselben Namen für Ihr propund verwenden solltendata , dh:

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

Anmerkung 2: Da fühle ich mich gibt es einige Verwirrung in Bezug auf propsund Reaktivität , empfehle ich Ihnen einen Blick haben auf diesen Thread


3
Dies ist eine großartige Antwort, aber ich denke auch, dass sie aktualisiert werden sollte, um die Ereignisbindung einzuschließen. Ein vom Kind ausgelöstes Ereignis kann das Elternteil aktualisieren, wodurch aktualisierte Requisiten an das Kind übergeben werden. Auf diese Weise bleibt die Quelle der Wahrheit über alle Komponenten hinweg erhalten. Es kann auch erwähnenswert sein, Vuex für die Verwaltung des Status zu erwähnen, der von mehreren Komponenten gemeinsam genutzt wird.
Wes Harper

@WesHarper, es ist auch möglich, den Modifikator .sync als Abkürzung zu verwenden, um das Aktualisierungsereignis des übergeordneten Elements automatisch abzurufen .
danb4r

47

Das Vue-Muster ist propsauf und eventsab. Es klingt einfach, ist aber beim Schreiben einer benutzerdefinierten Komponente leicht zu vergessen.

Ab Vue 2.2.0 können Sie das V-Modell (mit berechneten Eigenschaften ) verwenden. Ich habe festgestellt, dass diese Kombination eine einfache, saubere und konsistente Schnittstelle zwischen Komponenten schafft:

  • Alle propsan Ihre Komponente übergebenen Daten bleiben reaktiv (dh sie werden weder geklont noch erfordern sie eine watchFunktion zum Aktualisieren einer lokalen Kopie, wenn Änderungen erkannt werden).
  • Änderungen werden automatisch an das übergeordnete Element gesendet.
  • Kann mit mehreren Ebenen von Komponenten verwendet werden.

Eine berechnete Eigenschaft ermöglicht die getrennte Definition von Setter und Getter. Dadurch kann die TaskKomponente wie folgt umgeschrieben werden:

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

Die Modelleigenschaft definiert, welcher Funktion zugeordnet propist v-modelund welches Ereignis bei Änderungen ausgegeben wird. Sie können diese Komponente dann vom übergeordneten Element aus wie folgt aufrufen:

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

Die listLocalberechnete Eigenschaft bietet eine einfache Getter- und Setter-Schnittstelle innerhalb der Komponente (stellen Sie sich vor, es handelt sich um eine private Variable). Innerhalb können #task-templateSie rendern listLocalund es bleibt reaktiv (dh wenn parentListÄnderungen, wird es die TaskKomponente aktualisieren ). Sie können auch mutieren, listLocalindem Sie den Setter aufrufen (z. B. this.listLocal = newList), und die Änderung wird an das übergeordnete Element gesendet.

Das Besondere an diesem Muster ist, dass Sie es listLocalan eine untergeordnete Komponente von Task(using v-model) übergeben können und Änderungen von der untergeordneten Komponente an die Komponente der obersten Ebene weitergegeben werden.

Angenommen, wir haben eine separate EditTaskKomponente, um Änderungen an den Aufgabendaten vorzunehmen. Unter Verwendung des gleichen v-modelund berechneten Eigenschaftenmusters können wir listLocalan die Komponente übergeben (unter Verwendung v-model):

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

Wenn EditTaskaussendet eine Änderung es in geeigneter Weise ruft set()auf listLocalund damit propagieren das Ereignis auf die oberste Ebene. In ähnlicher Weise kann die EditTaskKomponente auch andere untergeordnete Komponenten (z. B. Formularelemente) mit aufrufen v-model.


1
Ich mache etwas Ähnliches außer mit Synchronisation. Das Problem ist, dass ich nach dem Ausgeben meines Änderungsereignisses eine andere Methode ausführen muss. Wenn die Methode jedoch ausgeführt wird und den Getter trifft, erhalte ich den alten Wert, da das Änderungsereignis vom Listener noch nicht erfasst / an das untergeordnete Element weitergegeben wurde. Dies ist ein Fall, in dem ich die Requisite mutieren möchte, damit meine lokalen Daten korrekt sind, bis sich das übergeordnete Update wieder nach unten ausbreitet. Irgendwelche Gedanken dazu?
Koga73

Ich vermute, dass es keine gute Praxis ist, den Getter vom Setter aus anzurufen. Was Sie in Betracht ziehen könnten, ist, Ihre berechnete Eigenschaft zu überwachen und dort Ihre Logik hinzuzufügen.
Chris

Requisiten runter und Events rauf haben bei mir geklickt. Wo wird dies in den Dokumenten von VueJs erklärt?
nebulousGirl

@nebulousGirl Werfen Sie einen Blick hier: vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
chris

Ich denke, dies ist die schmerzlosere Art, die Änderungen an der übergeordneten Komponente zu übernehmen.
Muhammad

36

Vue warnt Sie nur: Sie ändern die Requisite in der Komponente, aber wenn die übergeordnete Komponente erneut gerendert wird, wird "Liste" überschrieben und Sie verlieren alle Ihre Änderungen. Es ist also gefährlich, dies zu tun.

Verwenden Sie stattdessen die berechnete Eigenschaft wie folgt:

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

25
Was ist mit einer 2-Wege-Bindungsstütze?
Josh R.

7
Wenn Sie eine bidirektionale Datenbindung wünschen, sollten Sie benutzerdefinierte Ereignisse verwenden. Weitere Informationen finden Sie unter: vuejs.org/v2/guide/components.html#sync-Modifier Sie können die Requisite immer noch nicht direkt ändern. Sie benötigen ein Ereignis und eine Funktion, die kümmert sich um einen Requisitenwechsel für Sie.
Marco

22

Wenn Sie Lodash verwenden, können Sie die Requisite klonen, bevor Sie sie zurückgeben. Dieses Muster ist hilfreich, wenn Sie diese Requisite sowohl für Eltern als auch für Kinder ändern.

Lassen Sie uns sagen , dass wir prop haben Liste auf Komponenten Gitter .

In der übergeordneten Komponente

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

In der untergeordneten Komponente

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

19

Requisiten runter, Events rauf. Das ist Vues Muster. Der Punkt ist, dass, wenn Sie versuchen, Requisiten zu mutieren, die von einem Elternteil übergeben werden. Es funktioniert nicht und wird nur wiederholt von der übergeordneten Komponente überschrieben. Die untergeordnete Komponente kann nur ein Ereignis ausgeben, um die übergeordnete Komponente über etw zu informieren. Wenn Ihnen diese Einschränkungen nicht gefallen, können Sie VUEX verwenden (tatsächlich saugt dieses Muster die Struktur komplexer Komponenten an, Sie sollten VUEX verwenden!).


2
Es gibt auch eine Option für den
Eventbus

Der Ereignisbus wird in
Version

15

Sie sollten den Wert der Requisiten in der untergeordneten Komponente nicht ändern. Wenn Sie es wirklich ändern müssen, können Sie verwenden .sync. Genau wie dieser

<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

Gemäß den VueJs 2.0 sollten Sie eine Requisite innerhalb der Komponente nicht mutieren. Sie werden nur von ihren Eltern mutiert. Daher sollten Sie Variablen in Daten mit unterschiedlichen Namen definieren und durch Aktualisieren der tatsächlichen Requisiten auf dem neuesten Stand halten. Falls die Listen-Requisite von einem übergeordneten Element geändert wird, können Sie sie analysieren und mutableList zuweisen. Hier ist eine Komplettlösung.

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

Es verwendet mutableList, um Ihre Vorlage zu rendern, sodass Sie Ihre Listen-Requisite in der Komponente sicher aufbewahren.


Ist es nicht teuer, eine Uhr zu benutzen?
Kick Buttowski

6

Ändern Sie die Requisiten nicht direkt in Komponenten. Wenn Sie sie ändern müssen, legen Sie eine neue Eigenschaft wie folgt fest:

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

Und ändern Sie den Wert von listClone.


6

Die Antwort ist einfach: Sie sollten die direkte Prop-Mutation aufheben, indem Sie den Wert einigen lokalen Komponentenvariablen zuweisen (dies kann eine Dateneigenschaft sein, die mit Get-, Setter- oder Watchern berechnet wird).

Hier ist eine einfache Lösung mit dem Watcher.

<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 ist das, was ich verwende, um Dateneingabekomponenten zu erstellen, und es funktioniert einwandfrei. Alle neuen Daten, die vom übergeordneten Element gesendet werden (v-Modell (ed)), werden vom Wertebeobachter überwacht und der Eingabevariablen zugewiesen. Sobald die Eingabe empfangen wurde, können wir diese Aktion abfangen und Eingaben an das übergeordnete Element senden, die darauf hinweisen, dass Daten eingegeben werden aus dem Formularelement.


5

Ich habe mich auch diesem Problem gestellt. Die Warnung ist verschwunden, nachdem ich $onund verwendet habe $emit. Es ist so etwas wie Verwendung $onund $emitempfohlen, Daten von der untergeordneten Komponente zur übergeordneten Komponente zu senden.


4

Wenn Sie Requisiten mutieren möchten, verwenden Sie Objekt.

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

Komponente:

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

Nur als Hinweis, Sie müssen vorsichtig sein, wenn Sie Objekte mutieren. Javascript-Objekte werden als Referenz übergeben, es gibt jedoch eine Einschränkung: Die Referenz wird unterbrochen, wenn Sie eine Variable auf einen Wert setzen.
ira

3

Sie müssen eine berechnete Methode wie diese hinzufügen

component.vue

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

3

Einweg-Datenfluss Gemäß https://vuejs.org/v2/guide/components.html folgt die Komponente dem Einweg-Datenfluss. Alle Requisiten bilden eine Einweg-Bindung zwischen der untergeordneten Eigenschaft und dem übergeordneten Objekt Wenn die übergeordnete Eigenschaft aktualisiert wird, fließt sie zum untergeordneten Element, nicht jedoch umgekehrt. Dadurch wird verhindert, dass untergeordnete Komponenten versehentlich die übergeordneten Komponenten mutieren, wodurch der Datenfluss Ihrer App möglicherweise schwerer zu verstehen ist.

Außerdem werden jedes Mal, wenn die übergeordnete Komponente aktualisiert wird, alle Requisiten in den untergeordneten Komponenten mit dem neuesten Wert aktualisiert. Dies bedeutet, dass Sie nicht versuchen sollten, eine Requisite innerhalb einer untergeordneten Komponente zu mutieren. Wenn Sie dies tun, werden Sie von .vue in der Konsole gewarnt.

Es gibt normalerweise zwei Fälle, in denen es verlockend ist, eine Requisite zu mutieren: Die Requisite wird verwendet, um einen Anfangswert zu übergeben; Die untergeordnete Komponente möchte sie anschließend als lokale Dateneigenschaft verwenden. Die Requisite wird als Rohwert übergeben, der transformiert werden muss. Die richtige Antwort auf diese Anwendungsfälle lautet: Definieren Sie eine lokale Dateneigenschaft, die den Anfangswert der Requisite als Anfangswert verwendet:

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

Definieren Sie eine berechnete Eigenschaft, die aus dem Wert der Requisite berechnet wird:

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

2

Die Requisiten von Vue.j dürfen nicht mutiert werden, da dies in Vue als Anti-Pattern angesehen wird.

Der Ansatz, den Sie wählen müssen, besteht darin, eine Dateneigenschaft für Ihre Komponente zu erstellen, die auf die ursprüngliche Eigenschaft prop der Liste verweist

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

Jetzt wird Ihre Listenstruktur, die an die Komponente übergeben wird, über die dataEigenschaft Ihrer Komponente referenziert und mutiert :-)

Wenn Sie mehr als nur Ihre Listeneigenschaft analysieren möchten, verwenden Sie die Eigenschaft der Vue-Komponente computed. Auf diese Weise können Sie Ihre Requisiten eingehender mutieren.

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

Das obige Beispiel analysiert Ihre Listen- Requisite und filtert sie nur in aktive Listen-Tems, meldet sie für Schnitts und Kichern ab und gibt sie zurück.

Hinweis : Beide data& computedEigenschaften werden in der Vorlage gleich referenziert, z

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

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

Es kann leicht zu denken sein, dass eine computedEigenschaft (eine Methode) aufgerufen werden muss ... das tut es nicht


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

Vielleicht wird dies Ihren Bedürfnissen entsprechen.


2

Zur besten Antwort hinzufügen,

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

Das Festlegen von Requisiten durch ein Array ist für die Entwicklung / das Prototyping gedacht. Stellen Sie in der Produktion sicher, dass Sie Requisitentypen ( https://vuejs.org/v2/guide/components-props.html ) festlegen und einen Standardwert festlegen, falls die Requisite dies nicht hat wurde vom Elternteil als solches bevölkert.

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

Auf diese Weise erhalten Sie mindestens ein leeres Objekt mutableListanstelle eines JSON.parse-Fehlers, wenn es nicht definiert ist.


0

Vue.js betrachtet dies als Anti-Muster. Zum Beispiel deklarieren und setzen Sie einige Requisiten wie

this.propsVal = 'new Props Value'

Um dieses Problem zu lösen, müssen Sie einen Wert von den Requisiten zu den Daten oder der berechneten Eigenschaft einer Vue-Instanz wie folgt eingeben:

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

Das wird definitiv funktionieren.


0

Darüber hinaus für andere Personen mit folgendem Problem:

"Wenn der Requisitenwert nicht erforderlich ist und daher nicht immer zurückgegeben wird, werden die übergebenen Daten zurückgegeben undefined(anstatt leer)." Was den <select>Standardwert durcheinander bringen könnte , habe ich gelöst, indem ich überprüft habe, ob der Wert beforeMount()wie folgt eingestellt ist (und wenn nicht):

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

Ich möchte diese Antwort geben, um zu vermeiden, dass viel Code, Beobachter und berechnete Eigenschaften verwendet werden. In einigen Fällen kann dies eine gute Lösung sein:

Requisiten bieten eine einseitige Kommunikation.

Wenn Sie einen modalen show/hideKnopf mit einer Requisite haben, ist die beste Lösung für mich, ein Ereignis auszusenden:

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

Fügen Sie dann den Listener zum modalen Element hinzu:

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

(In diesem Fall ist die Requisite showwahrscheinlich unnötig, da Sie eine einfache v-if="show"direkt auf dem Basismodal verwenden können)


0

Ich persönlich schlage immer vor, wenn Sie die Requisiten mutieren müssen, sie zuerst an das berechnete Eigentum übergeben und von dort zurückkehren. Danach können Sie die Requisiten leicht mutieren, auch wenn Sie die Requisitenmutation verfolgen können, wenn diese von einem anderen mutiert werden Komponente auch oder wir können Sie auch sehen.


0

Da es sich bei Vue-Requisiten um einen Einwegdatenfluss handelt, wird verhindert, dass untergeordnete Komponenten versehentlich den Status des übergeordneten Elements ändern.

Aus dem offiziellen Vue-Dokument finden wir zwei Möglichkeiten, um diese Probleme zu lösen

  1. Wenn untergeordnete Komponenten Requisiten als lokale Daten verwenden möchten, definieren Sie am besten eine lokale Dateneigenschaft.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. Die Requisite wird als Rohwert übergeben, der transformiert werden muss. In diesem Fall ist es am besten, eine berechnete Eigenschaft anhand des Werts der Requisite zu definieren:

      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

YES!, Mutierende Attribute in vue2 sind Anti-Pattern. ABER ... Brechen Sie einfach die Regeln, indem Sie andere Regeln verwenden, und gehen Sie weiter! Sie müssen Ihrem Komponentenattribut im Paret-Bereich den Modifikator .sync hinzufügen. <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡 Es ist einfach, wir töten den Batman 😁


0

Für den Fall, dass TypeScript Ihre bevorzugte Sprache ist. der Entwicklung

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

und nicht

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



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

0

Unten ist eine Snackbar-Komponente, wenn ich die Snackbar- Variable direkt in das V-Modell wie dieses gebe, wenn es funktioniert, aber in der Konsole wird es einen Fehler als geben

Vermeiden Sie es, eine Requisite direkt zu mutieren, da der Wert bei jedem erneuten Rendern der übergeordneten Komponente überschrieben wird. Verwenden Sie stattdessen Daten oder berechnete Eigenschaften, die auf dem Wert der Requisite basieren.

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

Der richtige Weg, um diesen Mutationsfehler zu beseitigen, ist die Verwendung von 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>

In der Hauptkomponente, in die Sie diese Snackbar laden, können Sie einfach diesen Code ausführen

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

Das hat bei mir funktioniert

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.