Vue 2 - Mutasi props vue-warn


181

Saya mulai https://laracasts.com/series/learning-vue-step-by-step series. Saya berhenti pada pelajaran Vue, Laravel, dan AJAX dengan kesalahan ini:

vue.js: 2574 [Peringatan Vue]: Hindari memutasi prop secara langsung karena nilai akan ditimpa setiap kali komponen induk dirender ulang. Alih-alih, gunakan data atau properti yang dihitung berdasarkan nilai prop. Prop sedang dimutasi: "daftar" (ditemukan dalam komponen)

Saya memiliki kode ini di main.js

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

Saya tahu bahwa masalahnya sedang dibuat () ketika saya menimpa daftar prop, tapi saya seorang pemula di Vue, jadi saya benar-benar tidak tahu cara memperbaikinya. Adakah yang tahu bagaimana cara (dan tolong jelaskan alasannya) untuk memperbaikinya?


2
Tebak, Ini hanya pesan peringatan dan bukan kesalahan.
David R

Jawaban:


264

Ini ada hubungannya dengan fakta bahwa bermutasi prop secara lokal dianggap sebagai anti-pola dalam Vue 2

Apa yang harus Anda lakukan sekarang, jika Anda ingin bermutasi prop secara lokal , adalah mendeklarasikan bidang di Anda datayang menggunakan propsnilai sebagai nilai awal dan kemudian bermutasi salinan:

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

Anda dapat membaca lebih lanjut tentang ini di panduan resmi Vue.js


Catatan 1: Harap dicatat bahwa Anda tidak boleh menggunakan nama yang sama untuk Anda propdandata , yaitu:

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

Catatan 2: Karena saya merasa ada beberapa kebingungan tentang propsdan reaktivitas , saya sarankan Anda untuk melihat pada utas ini


3
Ini adalah jawaban yang bagus, tetapi saya juga berpikir bahwa itu harus diperbarui untuk menyertakan pengikatan acara. Suatu peristiwa yang dipicu dari anak dapat memperbarui orang tua, yang akan memberikan alat peraga yang diperbarui kepada anak. Dengan begitu, sumber kebenaran masih dipertahankan di semua komponen. Mungkin juga layak disebut Vuex untuk mengelola negara yang dibagi di beberapa komponen.
Wes Harper

@ WesHarper, dimungkinkan juga untuk menggunakan pengubah .sync sebagai singkatan untuk secara otomatis mendapatkan acara pembaruan orang tua.
danb4r

48

Pola Vue propsturun dan eventsnaik. Kedengarannya sederhana, tetapi mudah dilupakan saat menulis komponen khusus.

Pada Vue 2.2.0 Anda dapat menggunakan model-v (dengan properti yang dihitung ). Saya telah menemukan kombinasi ini menciptakan antarmuka yang sederhana, bersih, dan konsisten antara komponen:

  • Apa pun yang propsditeruskan ke komponen Anda tetap reaktif (yaitu, itu tidak dikloning juga tidak memerlukan watchfungsi untuk memperbarui salinan lokal ketika perubahan terdeteksi).
  • Perubahan secara otomatis dipancarkan ke induk.
  • Dapat digunakan dengan berbagai tingkatan komponen.

Properti yang dikomputasi memungkinkan penyetel dan pengambil ditetapkan secara terpisah. Ini memungkinkan Taskkomponen untuk ditulis ulang sebagai berikut:

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

The Model mendefinisikan properti yang propberhubungan dengan v-model, dan yang acara akan dipancarkan pada perubahan. Anda kemudian dapat memanggil komponen ini dari induknya sebagai berikut:

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

The listLocalproperti dihitung menyediakan getter dan setter antarmuka yang sederhana dalam komponen (berpikir itu seperti berada variabel pribadi). Di dalam #task-templateAnda dapat membuat listLocaldan itu akan tetap reaktif (yaitu, jika parentListperubahan itu akan memperbarui Taskkomponen). Anda juga dapat bermutasi listLocaldengan memanggil setter (mis., this.listLocal = newList) Dan itu akan memancarkan perubahan ke induk.

Apa yang hebat tentang pola ini adalah Anda dapat beralih listLocalke komponen anak dari Task(menggunakan v-model), dan perubahan dari komponen anak akan menyebar ke komponen tingkat atas.

Misalnya, kita memiliki EditTaskkomponen terpisah untuk melakukan beberapa jenis modifikasi pada data tugas. Dengan menggunakan v-modelpola properti yang sama dan dikomputasi, kita dapat beralih listLocalke komponen (menggunakan v-model):

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

Jika EditTaskmemancarkan perubahan dengan tepat akan memanggil set()pada listLocaldan dengan demikian menyebarkan acara ke tingkat atas. Demikian pula, EditTaskkomponen juga bisa memanggil komponen anak lainnya (seperti elemen formulir) menggunakan v-model.


1
Saya melakukan sesuatu yang serupa kecuali dengan sinkronisasi. Masalahnya adalah saya perlu menjalankan metode lain setelah memancarkan acara perubahan saya, namun ketika metode ini berjalan dan mengenai pengambil, saya mendapatkan nilai lama karena acara perubahan belum diambil oleh pendengar / disebarkan kembali ke anak. Ini adalah kasus di mana saya ingin bermutasi prop sehingga data lokal saya benar sampai pembaruan induk merambat kembali. Adakah pemikiran dalam hal ini?
koga73

Saya menduga memanggil pengambil dari setter itu bukan praktik yang baik. Yang mungkin Anda pertimbangkan adalah meletakkan jam di properti yang dihitung dan menambahkan logika di sana.
chris

menopang dan acara naik adalah apa yang diklik dengan saya. Di mana ini dijelaskan dalam dokumen VueJs?
nebulousGirl


Saya pikir ini adalah cara yang lebih mudah memancarkan perubahan pada komponen induk.
Muhammad

36

Vue hanya memperingatkan Anda: Anda mengubah penyangga dalam komponen, tetapi ketika komponen induk dirender kembali, "daftar" akan ditimpa dan Anda kehilangan semua perubahan Anda. Jadi berbahaya untuk melakukannya.

Gunakan properti yang dihitung sebagai gantinya:

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

25
Bagaimana dengan alat pengikat 2 arah?
Josh R.

7
Jika Anda ingin pengikatan data 2 arah, Anda harus menggunakan acara khusus, untuk info lebih lanjut baca: vuejs.org/v2/guide/components.html#sync-Modifier Anda masih tidak dapat memodifikasi prop secara langsung, Anda memerlukan acara dan fungsi yang menangani perubahan prop untuk Anda.
Marco

22

Jika Anda menggunakan Lodash, Anda dapat mengkloning prop sebelum mengembalikannya. Pola ini bermanfaat jika Anda memodifikasi penyangga itu pada orang tua dan anak.

Katakanlah kita memiliki daftar prop pada kisi komponen .

Dalam Komponen Induk

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

Dalam Komponen Anak

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

19

Alat peraga turun, acara naik. Itu Pola Vue. Intinya adalah bahwa jika Anda mencoba untuk mengubah alat peraga yang lewat dari orangtua. Itu tidak akan berfungsi dan hanya akan ditimpa berulang kali oleh komponen induk. Komponen anak hanya dapat memancarkan suatu peristiwa untuk memberi tahu komponen induk untuk melakukan sth. Jika Anda tidak menyukai batasan ini, Anda dapat menggunakan VUEX (sebenarnya pola ini akan menyedot struktur komponen yang kompleks, Anda harus menggunakan VUEX!)


2
Ada juga opsi untuk menggunakan bus acara
Steven Pribilinskiy

event bus tidak didukung secara asli di vue 3. github.com/vuejs/rfcs/pull/118
AlexMA

15

Anda tidak boleh mengubah nilai alat peraga dalam komponen anak. Jika Anda benar-benar perlu mengubahnya, Anda dapat menggunakannya .sync. Seperti ini

<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

Menurut VueJs 2.0, Anda tidak harus bermutasi prop di dalam komponen. Mereka hanya bermutasi oleh orang tua mereka. Karena itu, Anda harus mendefinisikan variabel dalam data dengan nama yang berbeda dan tetap memperbaruinya dengan menonton alat peraga yang sebenarnya. Jika prop daftar diubah oleh induk, Anda dapat menguraikannya dan menetapkannya ke mutableList. Ini solusi lengkapnya.

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

Ini menggunakan mutableList untuk membuat template Anda, sehingga Anda menyimpan prop daftar Anda aman di komponen.


bukankah arloji mahal untuk digunakan?
Kick Buttowski

6

jangan mengubah alat peraga secara langsung dalam komponen. jika Anda perlu mengubahnya setel properti baru seperti ini:

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

Dan ubah nilai listClone.


6

Jawabannya sederhana, Anda harus memecah mutasi prop langsung dengan menetapkan nilai ke beberapa variabel komponen lokal (bisa berupa properti data, dihitung dengan getter, setter, atau pengamat).

Inilah solusi sederhana menggunakan pengamat.

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

Ini yang saya gunakan untuk membuat komponen input data dan berfungsi dengan baik. Setiap data baru yang dikirim (model-v (ed)) dari induk akan diawasi oleh pengamat nilai dan ditugaskan ke variabel input dan setelah input diterima, kita dapat menangkap tindakan itu dan memancarkan input ke induk yang menyarankan bahwa data adalah input dari elemen form.


5

Saya menghadapi masalah ini juga. Peringatan hilang setelah saya menggunakan $ondan $emit. Ini seperti penggunaan $ondan $emitdirekomendasikan untuk mengirim data dari komponen anak ke komponen induk.


4

Jika Anda ingin mengubah alat peraga - gunakan objek.

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

komponen:

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

Sama seperti catatan, Anda harus berhati-hati saat bermutasi objek. Objek Javascript dilewatkan oleh referensi, tetapi ada peringatan: Referensi rusak ketika Anda menetapkan variabel sama dengan nilai.
ira

3

Anda perlu menambahkan metode yang dihitung seperti ini

komponen.vue

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

3

aliran data satu arah, menurut https://vuejs.org/v2/guide/components.html , komponen mengikuti aliran data satu arah, semua alat peraga membentuk ikatan satu arah yang turun antara properti anak dan orang tua satu, ketika properti induk diperbarui, itu akan mengalir ke anak tetapi tidak sebaliknya, ini mencegah komponen anak dari secara tidak sengaja bermutasi pada orang tua, yang dapat membuat aliran data aplikasi Anda lebih sulit untuk dipahami.

Selain itu, setiap kali komponen induk diperbarui, semua alat peraga dalam komponen turunan akan disegarkan dengan nilai terbaru. Ini berarti Anda tidak boleh mencoba untuk memutasikan penyangga di dalam komponen anak. Jika Anda melakukannya .vue akan memperingatkan Anda di konsol.

Biasanya ada dua kasus di mana tergoda untuk bermutasi prop: Prop digunakan untuk memberikan nilai awal; komponen anak ingin menggunakannya sebagai properti data lokal sesudahnya. Prop disahkan sebagai nilai mentah yang perlu diubah. Jawaban yang tepat untuk kasus penggunaan ini adalah: Tentukan properti data lokal yang menggunakan nilai awal prop sebagai nilai awalnya:

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

Tetapkan properti yang dihitung yang dihitung dari nilai prop:

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

2

Alat peraga Vue.js tidak boleh dimutasi karena ini dianggap sebagai Anti-Pola di Vue.

Pendekatan yang perlu Anda ambil adalah membuat properti data pada komponen Anda yang mereferensikan properti prop asli daftar

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

Sekarang struktur daftar Anda yang diteruskan ke komponen direferensikan dan dimutasi melalui dataproperti komponen Anda :-)

Jika Anda ingin melakukan lebih dari sekadar mengurai properti daftar Anda, gunakan computedproperti komponen Vue . Ini memungkinkan Anda untuk membuat lebih banyak mutasi mendalam ke alat peraga Anda.

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

Contoh di atas mem-parsing prop daftar Anda dan memfilternya hanya ke list-tems yang aktif , mencatatnya untuk schnitts dan cekikikan dan mengembalikannya.

catatan : keduanya data& computedproperti direferensikan dalam template misalnya sama

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

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

Sangat mudah untuk berpikir bahwa sebuah computedproperti (menjadi metode) perlu disebut ... tidak


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

Mungkin ini akan memenuhi kebutuhan Anda.


2

Menambah jawaban terbaik,

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

Mengatur alat peraga oleh array dimaksudkan untuk dev / prototyping, dalam produksi pastikan untuk menetapkan jenis alat peraga ( https://vuejs.org/v2/guide/components-props.html ) dan tetapkan nilai default jika prop tidak memiliki telah diisi oleh orang tua, seperti itu.

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

Dengan cara ini Anda setidaknya mendapatkan objek kosong mutableListdaripada kesalahan JSON.parse jika tidak terdefinisi.


0

Vue.js menganggap ini sebagai anti-pola. Misalnya, mendeklarasikan dan mengatur beberapa alat peraga seperti

this.propsVal = 'new Props Value'

Jadi untuk mengatasi masalah ini, Anda harus mengambil nilai dari properti ke data atau properti yang dihitung dari instance Vue, seperti ini:

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

Ini pasti akan berhasil.


0

Selain hal di atas, untuk orang lain yang memiliki masalah berikut:

"Jika nilai properti tidak diperlukan dan dengan demikian tidak selalu dikembalikan, data yang dikirimkan akan kembali undefined(bukan kosong)". Yang bisa mengacaukan <select>nilai default, saya menyelesaikannya dengan memeriksa apakah nilainya diatur beforeMount()(dan setel jika tidak) sebagai berikut:

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

Saya ingin memberikan jawaban ini yang membantu menghindari menggunakan banyak kode, pengamat, dan properti yang dihitung. Dalam beberapa kasus ini bisa menjadi solusi yang baik:

Alat peraga dirancang untuk menyediakan komunikasi satu arah.

Ketika Anda memiliki show/hidetombol modal dengan properti, solusi terbaik bagi saya adalah memancarkan acara:

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

Kemudian tambahkan pendengar ke elemen modal:

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

(Dalam hal ini prop showmungkin tidak perlu karena Anda dapat menggunakan mudah v-if="show"langsung pada basis-modal)


0

Saya pribadi selalu menyarankan jika Anda perlu mengubah alat peraga, pertama-tama berikan mereka ke properti yang dihitung dan kembali dari sana, setelah itu seseorang dapat memutasi alat peraga dengan mudah, bahkan pada saat itu Anda dapat melacak mutasi alat peraga, jika mereka dimutasikan dari yang lain komponen juga atau kami dapat Anda tonton juga.


0

Karena alat peraga Vue adalah aliran data satu arah, ini mencegah komponen anak dari secara tidak sengaja mengubah status induk.

Dari dokumen Vue resmi, kami akan menemukan 2 cara untuk menyelesaikan masalah ini

  1. Jika komponen anak ingin menggunakan properti sebagai data lokal, yang terbaik adalah mendefinisikan properti data lokal.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. Prop disahkan sebagai nilai mentah yang perlu diubah. Dalam hal ini, yang terbaik adalah mendefinisikan properti yang dihitung menggunakan nilai prop:

      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

YA !, atribut yang bermutasi di vue2 adalah anti-pola. TAPI ... Hancurkan saja peraturan dengan menggunakan aturan lain, dan teruskan! Yang Anda butuhkan adalah menambahkan .sync modifier ke atribut komponen Anda dalam lingkup paret. <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡 Sederhana saja kita membunuh batman 😁


0

Untuk saat TypeScript adalah bahasa pilihan Anda. pembangunan

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

dan tidak

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



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

0

Di bawah ini adalah komponen snack bar, ketika saya memberikan variabel snackbar langsung ke v-model seperti ini jika akan bekerja tetapi di konsol, itu akan memberikan kesalahan seperti

Hindari mengubah prop secara langsung karena nilai akan ditimpa setiap kali komponen induk dirender kembali. Alih-alih, gunakan data atau properti yang dihitung berdasarkan nilai prop.

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

Cara yang benar untuk menghilangkan kesalahan mutasi ini adalah menggunakan pengamat

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

Jadi di komponen utama di mana Anda akan memuat snack bar ini, Anda bisa melakukan kode ini

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

Ini Berhasil untuk saya

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.