Vue 2 - Mutowanie rekwizytów vue-warn


181

Zacząłem https://laracasts.com/series/learning-vue-step-by-step series. Zatrzymałem się na lekcji Vue, Laravel i AJAX z tym błędem:

vue.js: 2574 [Vue warn]: Unikaj bezpośredniego modyfikowania właściwości, ponieważ wartość zostanie nadpisana przy każdym ponownym renderowaniu komponentu nadrzędnego. Zamiast tego użyj danych lub obliczonej właściwości opartej na wartości właściwości. Mutacja rekwizytu: „lista” (znaleziona w komponencie)

Mam ten kod w main.js.

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

Wiem, że problem pojawia się w created (), kiedy nadpisuję właściwość listy, ale jestem nowicjuszem w Vue, więc kompletnie nie wiem, jak to naprawić. Czy ktoś ma pomysł, jak (i ​​proszę wyjaśnić, dlaczego) to naprawić?


2
Chyba to tylko ostrzeżenie, a nie błąd.
David R

Odpowiedzi:


264

Ma to związek z faktem, że lokalna mutacja rekwizytu jest uważana za anty-wzorzec w Vue 2

To, co powinieneś teraz zrobić, jeśli chcesz lokalnie zmutować właściwość , to zadeklarować w swoim polu, dataktóre używa propswartości jako wartości początkowej, a następnie zmutować kopię:

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

Możesz przeczytać więcej na ten temat w oficjalnym przewodniku Vue.js.


Uwaga 1: Należy pamiętać, że należy nie używać tego samego nazwę dla propadata , czyli:

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

Uwaga 2: Ponieważ czuję, że istnieje pewne zamieszanie dotyczące propsi reaktywności , proponuję rzucić okiem na ten wątek


3
To świetna odpowiedź, ale uważam też, że należy ją zaktualizować, aby zawierała wiązanie zdarzeń. Zdarzenie wywołane przez dziecko może zaktualizować rodzica, który przekaże zaktualizowane właściwości dziecku. W ten sposób źródło prawdy jest nadal utrzymywane we wszystkich komponentach. Warto również wspomnieć o Vuex do zarządzania stanem, który jest współdzielony przez wiele komponentów.
Wes Harper

@WesHarper, możliwe jest również użycie modyfikatora .sync jako skrótu, aby automatycznie uzyskać zdarzenie update rodzica.
danb4r

48

Wzór Vue jest w propsdół i w eventsgórę. Brzmi prosto, ale łatwo o tym zapomnieć, pisząc komponent niestandardowy.

Od wersji Vue 2.2.0 możesz używać modelu v (z obliczonymi właściwościami ). Odkryłem, że ta kombinacja tworzy prosty, czysty i spójny interfejs między komponentami:

  • Każdy propsprzekazany do twojego komponentu pozostaje reaktywny (tj. Nie jest sklonowany ani nie wymaga watchfunkcji aktualizowania kopii lokalnej po wykryciu zmian).
  • Zmiany są automatycznie wysyłane do rodzica.
  • Może być używany z wieloma poziomami komponentów.

Obliczona właściwość umożliwia oddzielne definiowanie metody ustawiającej i pobierającej. Pozwala to Taskna przepisanie komponentu w następujący sposób:

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

Właściwość modelu określa, z którym elementem propjest skojarzony v-modeli które zdarzenie będzie emitowane przy zmianie. Następnie możesz wywołać ten komponent od rodzica w następujący sposób:

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

Właściwość listLocalcomputed zapewnia prosty interfejs pobierający i ustawiający w komponencie (myśl o tym jak o zmiennej prywatnej). Wewnątrz #task-templatemożesz renderować listLocali pozostanie reaktywny (tj. Jeśli parentListzmieni się, zaktualizuje Taskkomponent). Możesz także dokonać mutacji listLocal, wywołując ustawiającego (np. this.listLocal = newList), A wyśle ​​on zmianę do rodzica.

Wspaniałe w tym wzorcu jest to, że możesz przekazać listLocaldo komponentu potomnego Task(używając v-model), a zmiany z komponentu potomnego będą propagowane do komponentu najwyższego poziomu.

Załóżmy na przykład, że mamy oddzielny EditTaskkomponent do wykonywania pewnych modyfikacji danych zadania. Korzystając z tego samego v-modeli obliczonego wzorca właściwości możemy przekazać listLocalkomponentowi (używając v-model):

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

Jeśli EditTaskemituje zmiana będzie to odpowiednio zadzwonić set()na listLocaliw ten sposób propagować wydarzenie na najwyższym poziomie. Podobnie EditTaskkomponent może również wywoływać inne komponenty podrzędne (takie jak elementy formularzy) za pomocą v-model.


1
Robię coś podobnego, z wyjątkiem synchronizacji. Problem polega na tym, że muszę uruchomić inną metodę po wyemitowaniu mojego zdarzenia change, jednak gdy metoda działa i trafia do gettera, otrzymuję starą wartość, ponieważ zdarzenie change nie zostało jeszcze odebrane przez odbiorcę / propagowane z powrotem do dziecka. Jest to przypadek, w którym chcę zmutować właściwość, aby moje dane lokalne były poprawne, dopóki aktualizacja nadrzędna nie zostanie z powrotem propagowana. Jakieś przemyślenia w tym zakresie?
koga73

Podejrzewam, że wywołanie gettera od setera nie jest dobrą praktyką. Możesz rozważyć ustawienie zegarka na obliczonej właściwości i dodanie tam swojej logiki.
chris,

rekwizyty w dół i wydarzenia w górę, to właśnie mnie zaskoczyło. Gdzie jest to wyjaśnione w dokumentach VueJs?
nebulousGirl


Myślę, że jest to bardziej bezbolesny sposób emitowania zmian w komponencie macierzystym.
Muhammad

36

Vue tylko ostrzega: zmieniasz właściwość w komponencie, ale kiedy komponent nadrzędny ponownie się wyrenderuje, „lista” zostanie nadpisana i stracisz wszystkie zmiany. Jest to więc niebezpieczne.

Zamiast tego użyj właściwości obliczonej w ten sposób:

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

25
A co z dwukierunkowym wiązaniem?
Josh R.

7
Jeśli chcesz dwukierunkowe wiązanie danych, powinieneś użyć niestandardowych zdarzeń. Aby uzyskać więcej informacji, przeczytaj: vuejs.org/v2/guide/components.html#sync-Modifier Nadal nie możesz bezpośrednio modyfikować właściwości, potrzebujesz zdarzenia i funkcji, które obsługuje zmianę rekwizytu za Ciebie.
Marco

22

Jeśli używasz Lodash, możesz sklonować rekwizyt przed zwróceniem go. Ten wzorzec jest przydatny, jeśli zmodyfikujesz ten element zarówno dla rodzica, jak i dziecka.

Powiedzmy, że mamy listę właściwości na siatce komponentów .

W komponencie nadrzędnym

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

W komponencie podrzędnym

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

19

Rekwizyty w dół, wydarzenia w górę. To wzór Vue. Chodzi o to, że jeśli spróbujesz zmutować rekwizyty przechodzące od rodzica. To nie zadziała i po prostu zostanie wielokrotnie nadpisane przez komponent nadrzędny. Składnik podrzędny może emitować tylko zdarzenie, aby powiadomić składnik nadrzędny o wykonaniu czegoś. Jeśli nie podoba ci się te ograniczenie, możesz użyć VUEX (w rzeczywistości ten wzór będzie wessał złożoną strukturę komponentów, powinieneś użyć VUEX!)


2
Istnieje również opcja skorzystania z autobusu eventowego
Steven Pribilinskiy,

Event Bus nie jest natywnie obsługiwany w vue 3. github.com/vuejs/rfcs/pull/118
AlexMA

15

Nie powinieneś zmieniać wartości właściwości w komponencie potomnym. Jeśli naprawdę potrzebujesz go zmienić, możesz użyć .sync. Takie jak to

<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

Zgodnie z VueJs 2.0 nie powinieneś modyfikować właściwości wewnątrz komponentu. Mutują je tylko rodzice. Dlatego powinieneś zdefiniować zmienne w danych o różnych nazwach i aktualizować je, obserwując rzeczywiste rekwizyty. W przypadku zmiany właściwości listy przez rodzica, możesz ją przeanalizować i przypisać do mutableList. Oto kompletne rozwiązanie.

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

Używa mutableList do renderowania twojego szablonu, więc zachowujesz bezpieczną propozycję listy w komponencie.


czy zegarek nie jest drogi w użyciu?
Kick Buttowski

6

nie zmieniaj właściwości bezpośrednio w komponentach. jeśli chcesz to zmienić, ustaw nową właściwość w następujący sposób:

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

I zmień wartość listClone.


6

Odpowiedź jest prosta, należy przerwać bezpośrednią mutację prop, przypisując wartość do niektórych lokalnych zmiennych składowych (może to być właściwość data, obliczana za pomocą metod pobierających, ustawiających lub obserwatorów).

Oto proste rozwiązanie przy użyciu obserwatora.

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

To jest to, czego używam do tworzenia dowolnych komponentów do wprowadzania danych i działa dobrze. Wszelkie nowe dane wysłane (v-model (ed)) od rodzica będą obserwowane przez obserwatora wartości i są przypisywane do zmiennej wejściowej, a po otrzymaniu danych wejściowych możemy złapać tę akcję i wysłać dane wejściowe do rodzica, sugerując, że dane są wprowadzane z elementu formularza.


5

Miałem też do czynienia z tym problemem. Ostrzeżenie zniknęło po użyciu $oni $emit. Jest to coś w rodzaju użycia $oni $emitzalecanego przesyłania danych z komponentu podrzędnego do komponentu nadrzędnego.


4

Jeśli chcesz zmienić właściwości - użyj obiektu.

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

składnik:

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

Tak jak uwaga, musisz uważać podczas mutowania obiektów. Obiekty JavaScript są przekazywane przez odwołanie, ale istnieje zastrzeżenie: odniesienie jest przerywane, gdy ustawisz zmienną równą wartości.
ira

3

Musisz dodać taką metodę obliczeniową

component.vue

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

3

jednokierunkowy przepływ danych, zgodnie z https://vuejs.org/v2/guide/components.html , komponent podąża za jednokierunkowym przepływem danych, wszystkie rekwizyty tworzą jednokierunkowe powiązanie między własnością podrzędną a rodzicem po pierwsze, kiedy właściwość nadrzędna zostanie zaktualizowana, będzie spływać do dziecka, ale nie na odwrót. Zapobiega to przypadkowej mutacji składników podrzędnych, co może utrudnić zrozumienie przepływu danych w aplikacji.

Ponadto za każdym razem, gdy komponent nadrzędny jest aktualizowany, wszystkie właściwości w komponentach potomnych zostaną odświeżone o najnowszą wartość. Oznacza to, że nie powinieneś próbować modyfikować właściwości wewnątrz komponentu potomnego. Jeśli to zrobisz .vue wyświetli ostrzeżenie w konsoli.

Istnieją zazwyczaj dwa przypadki, w których kuszące jest modyfikowanie właściwości: Właściwość służy do przekazywania wartości początkowej; komponent potomny chce go później użyć jako lokalnej właściwości danych. Rekwizyt jest przekazywany jako wartość surowa, którą należy przekształcić. Prawidłową odpowiedzią na te przypadki użycia są: Zdefiniuj lokalną właściwość danych, która używa wartości początkowej właściwości jako wartości początkowej:

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

Zdefiniuj obliczoną właściwość, która jest obliczana na podstawie wartości właściwości:

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

2

Rekwizyty Vue.js nie powinny być modyfikowane, ponieważ w Vue jest to uważane za Anty-Pattern.

Podejście trzeba będzie podjąć jest tworzenie własności danych na składniku że odniesienia oryginalny prop własnością listy

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

Teraz twoja struktura listy, która jest przekazywana do komponentu, jest przywoływana i mutowana przez datawłaściwość twojego komponentu :-)

Jeśli chcesz zrobić coś więcej niż tylko przeanalizować właściwość listy, użyj właściwości komponentu Vue computed. To pozwoli ci na głębsze mutacje twoich rekwizytów.

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

Powyższy przykład analizuje twoją właściwość listy i filtruje ją tylko do aktywnych list- temów, wylogowuje ją w poszukiwaniu schnittów i chichotów i zwraca.

Uwaga : w szablonie znajdują się odwołania do obu data& computedwłaściwości, np

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

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

Łatwo jest pomyśleć, że computedwłaściwość (będąca metodą) musi zostać nazwana ... tak nie jest


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

Może to zaspokoi Twoje potrzeby.


2

Dodając do najlepszej odpowiedzi,

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

Ustawienie właściwości przez tablicę jest przeznaczone do tworzenia / tworzenia prototypów, w produkcji upewnij się, że ustawiłeś typy właściwości ( https://vuejs.org/v2/guide/components-props.html ) i ustaw domyślną wartość na wypadek, gdyby prop nie zostały wypełnione przez rodzica, jako takie.

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

W ten sposób otrzymasz przynajmniej pusty obiekt mutableListzamiast błędu JSON.parse, jeśli jest on niezdefiniowany.


0

Vue.js uważa to za anty-wzór. Na przykład deklarowanie i ustawianie niektórych rekwizytów, takich jak

this.propsVal = 'new Props Value'

Aby rozwiązać ten problem, musisz wziąć wartość z właściwości do danych lub obliczonej właściwości instancji Vue, na przykład:

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

To na pewno zadziała.


0

Oprócz powyższego, dla innych osób mających następujący problem:

„Jeśli wartość właściwości nie jest wymagana i nie zawsze jest zwracana, przekazane dane zwróciłyby undefined(zamiast byłyby puste)”. Co może zepsuć <select>wartość domyślną, rozwiązałem to, sprawdzając, czy wartość jest ustawiona w beforeMount()(i ustaw ją, jeśli nie) w następujący sposób:

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

Chcę udzielić odpowiedzi, która pomoże uniknąć używania dużej ilości kodu, obserwatorów i obliczonych właściwości. W niektórych przypadkach może to być dobre rozwiązanie:

Rekwizyty mają na celu zapewnienie jednostronnej komunikacji.

Kiedy masz show/hideprzycisk modalny z rekwizytem, ​​najlepszym rozwiązaniem dla mnie jest emitowanie zdarzenia:

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

Następnie dodaj detektor do elementu modalnego:

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

(W tym przypadku rekwizyt showprawdopodobnie nie jest potrzebny, ponieważ można użyć łatwego v-if="show"bezpośrednio na bazie modalnej)


0

Osobiście zawsze sugeruję, że jeśli musisz zmutować rekwizyty, najpierw przekaż je do obliczonej własności i wróć stamtąd, potem można łatwo mutować rekwizyty, nawet wtedy, gdy możesz śledzić mutację rekwizytów, jeśli te są mutowane z innego komponent też lub możemy obejrzeć również.


0

Ponieważ właściwości Vue to jednokierunkowy przepływ danych, zapobiega to przypadkowej zmianie stanu rodzica przez komponenty potomne.

Z oficjalnego dokumentu Vue znajdziemy 2 sposoby rozwiązania tego problemu

  1. jeśli komponent potomny chce używać właściwości jako danych lokalnych, najlepiej jest zdefiniować lokalną właściwość danych.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. Rekwizyt jest przekazywany jako wartość surowa, którą należy przekształcić. W tym przypadku najlepiej jest zdefiniować obliczoną właściwość za pomocą wartości właściwości:

      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

TAK !, mutowanie atrybutów w vue2 jest anty-wzorcem. ALE ... Po prostu złam zasady, stosując inne zasady i idź naprzód! To, czego potrzebujesz, to dodanie modyfikatora .sync do atrybutu komponentu w zakresie paret. <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡 To proste, że zabijemy batmana 😁


0

Kiedy TypeScript jest preferowanym językiem. rozwoju

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

i nie

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



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

0

Poniżej znajduje się składnik batonika, kiedy podaję batonik zmienną bezpośrednio do modelu v w ten sposób, jeśli zadziała, ale w konsoli da błąd jako

Unikaj bezpośredniego modyfikowania właściwości, ponieważ wartość zostanie nadpisana przy każdym ponownym renderowaniu komponentu nadrzędnego. Zamiast tego użyj danych lub obliczonej właściwości opartej na wartości właściwości.

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

Poprawnym sposobem na pozbycie się tego błędu mutacji jest użycie obserwatora

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

Więc w głównym komponencie, w którym załadujesz ten batonik, możesz po prostu zrobić ten kod

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

To zadziałało dla mnie

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.