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

Dariusz Chowański
źródło
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

ira
źródło
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.

chris
źródło
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);
        }
    }
});
Sealla
źródło
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)
    }
}
Za imię
źródło
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!)

edison xue
źródło
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'
})
Potato Running
źródło
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.

burak buruk
źródło
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.

Tango5614
źródło
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.

Vectrobyte
źródło
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.

ihsanberahim
źródło
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";
  }
}
Aiwass 418
źródło
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);
    }
}
ubastosir
źródło
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()
  }
}
mryang
źródło
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

Francis Leigh
źródło
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.

DP
źródło
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.

YuuwakU
źródło
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.

the_haystacker
źródło
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>
Maroun Melhem
źródło
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)

Panie Web
źródło
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ż.

mohit dutt
źródło
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
      }
    
    
flypan
źródło
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 😁

Ali
źródło
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;
}
cRAN
źródło
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

lokesh
źródło