Vue 2 - Мутація реквізиту vue-warn


181

Я розпочав серію https://laracasts.com/series/learning-vue-step-by-step . Я зупинився на уроці Vue, Laravel та AJAX з цією помилкою:

vue.js: 2574 [Vue warn]: Уникайте мутації опори безпосередньо, оскільки значення буде перезаписано кожного разу, коли батьківський компонент повторно надає. Натомість використовуйте дані або обчислені властивості на основі значення опори. Мутація реквізита: "список" (знайдено в компоненті)

У мене цей код в main.js

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

Я знаю, що проблема полягає у створеному (), коли я перезаписую підказку списку, але я новачок у Vue, тому я абсолютно не знаю, як її виправити. У когось є ідея, як (і, будь ласка, поясніть, чому) це виправити?


2
Здогадайтесь, це просто попередження, а не помилка.
David R

Відповіді:


264

Це пов’язано з тим, що вимкнення опори на місцевому рівні вважається анти-зразком у Vue 2

Що вам слід зробити зараз, якщо ви хочете вимкнути опору локально , це оголосити поле у ​​вашому, dataяке використовує propsзначення як його початкове значення, а потім вимкнути копію:

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

Детальніше про це ви можете прочитати в офіційному довіднику Vue.js


Примітка 1: Будь ласка , зверніть увагу , що ви повинні НЕ використовувати таке ж ім'я для вашого propіdata , а саме:

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

Примітка 2: Так як я відчуваю , що є деяка плутанина щодо propsі реакційна здатність , я пропоную вам поглянути на цю нитку


3
Це чудова відповідь, але я також вважаю, що її слід оновлювати, щоб включати прив'язку подій. Подія, ініційована у дитини, може оновити батька, який передасть оновлені реквізити дитині. Таким чином, джерело істини все ще зберігається в усіх компонентах. Можливо, варто також згадати Vuex для управління станом, який поділяється на декілька компонентів.
Уес Харпер

@WesHarper, також можна використовувати модифікатор .sync як стенограму для автоматичного отримання події оновлення батьків.
danb4r

48

Візерунок Vue propsвниз і eventsвгору. Це звучить просто, але його легко забути при написанні користувацького компонента.

Станом на Vue 2.2.0 ви можете використовувати v-модельобчисленими властивостями ). Я виявив, що це поєднання створює простий, чистий та послідовний інтерфейс між компонентами:

  • Будь-який propsпереданий вашому компоненту залишається реактивним (тобто він не клонований, а також не потрібна watchфункція оновлення локальної копії при виявленні змін).
  • Зміни автоматично надсилаються до батьків.
  • Можна використовувати з різними рівнями компонентів.

Обчислена властивість дозволяє сетеру та геттеру окремо визначатись. Це дозволяє Taskпереписати компонент наступним чином:

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

У модель визначає нерухомість , яка propасоціюється з v-model, і яка подія буде виділятися на змінах. Потім ви можете зателефонувати цьому компоненту від батьків наступним чином:

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

listLocalОбчислення властивість забезпечує простий і геттер сетер інтерфейс всередині компонента (думаю про нього , як бути закритою змінної). Всередині #task-templateви можете візуалізувати, listLocalі він залишатиметься реактивним (тобто, якщо parentListзміни будуть оновлені, Taskкомпонент). Ви також можете мутувати listLocal, зателефонувавши до сетера (наприклад, this.listLocal = newList), і він передасть зміну батькові.

Що в цьому шаблоні чудово, це те, що ви можете перейти listLocalдо дочірнього компонента Task(використовуючи v-model), а зміни від дочірнього компонента поширяться на компонент верхнього рівня.

Наприклад, скажімо, у нас є окремий EditTaskкомпонент для внесення змін до даних завдання. Використовуючи той самий v-modelі обчислений шаблон властивостей, ми можемо перейти listLocalдо компонента (використовуючи v-model):

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

Якщо EditTaskвипускає зміна буде відповідним чином зателефонувати set()на listLocalі тим самим поширити подія до рівня верхнього. Аналогічно, EditTaskкомпонент також може викликати інші дочірні компоненти (наприклад, елементи форми), використовуючи v-model.


1
Я роблю щось подібне, крім синхронізації. Проблема полягає в тому, що мені потрібно запустити інший метод після передачі моєї події зміни, однак, коли метод запускається і потрапляє на гетьтер, я отримую старе значення, оскільки подія зміни ще не була підібрана слухачем / передається назад дитині. Це випадок, коли я хочу вимкнути опору, щоб мої локальні дані були правильними, поки батьківське оновлення не поширюється назад вниз. Якісь думки з цього приводу?
koga73

Я підозрюю, що дзвонити геттеру від сетера не є хорошою практикою. Ви можете розглянути можливість встановити годинник на обчислену властивість і додати туди свою логіку.
chris

підскакує і події - це те, що натиснуло на мене. Де це пояснено в документах VueJs?
nebulousGirl

@nebulousGirl Подивіться тут: vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
chris

Я думаю, що це більш безболісний спосіб випромінювати зміни батьківського компонента.
Мухаммед

36

Vue просто попереджає вас: ви зміните опору в компоненті, але коли батьківський компонент повторно відобразиться, "список" буде перезаписаний, і ви втратите всі свої зміни. Тож робити це небезпечно.

Використовуйте обчислену властивість замість цього:

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

25
А як щодо двосторонньої зв'язувальної опори?
Джош Р.

7
Якщо ви хочете двосторонній прив'язки даних, вам слід використовувати спеціальні події, для отримання додаткової інформації читайте: vuejs.org/v2/guide/components.html#sync-Modifier Ви все ще не можете безпосередньо змінювати опору, вам потрібна подія та функція, яка обробляє зміну опори для вас.
Марко

22

Якщо ви використовуєте Lodash, ви можете клонувати опору перед поверненням. Ця модель корисна, якщо ви змінюєте опору для батьків і дитини.

Скажімо , у нас є пропелер список на компонент сітки .

У батьківській складовій

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

У дочірніх компонентах

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

19

Підпирає, події вгору. Отакий зразок Вує Справа в тому, що якщо ви намагаєтесь мутувати реквізит, що передається від батьків. Він не працює, і він просто повторно перезаписується батьківським компонентом. Дочірній компонент може випромінювати лише подію, щоб сповістити батьківський компонент робити що-небудь. Якщо вам не подобаються ці обмеження, ви можете використовувати VUEX (фактично ця модель буде всмоктуватися в складну структуру компонентів, вам слід використовувати VUEX!)


2
Також є можливість скористатись автобусом події
Стівен Прибілінський

автобус події не підтримується в vue 3. github.com/vuejs/rfcs/pull/118
AlexMA

15

Не слід змінювати значення реквізиту в дочірніх компонентах. Якщо вам дійсно потрібно змінити його, ви можете використовувати .sync. Просто так

<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

Відповідно до VueJs 2.0, ви не повинні мутувати опору всередині компонента. Їх мутують лише батьки. Тому слід визначати змінні в даних з різними іменами та постійно оновлювати їх, переглядаючи фактичні реквізити. У випадку, якщо опора списку зміниться батьком, ви можете проаналізувати його та призначити mutableList. Ось повне рішення.

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

Він використовує mutableList для візуалізації шаблону, таким чином ви зберігаєте свою підтримку списку в компоненті.


це не дивитись дорого, щоб їх використовувати?
Кіт Бутовський

6

не змінюйте реквізит безпосередньо в компонентах. Якщо вам потрібно змінити, встановіть нове властивість на зразок цього:

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

І змінити значення listClone.


6

Відповідь проста: вам слід порушити пряму мутацію опор, призначивши значення деяким локальним змінним компонентів (це може бути властивість даних, обчислена з геттерами, сетерами чи спостерігачами).

Ось просте рішення за допомогою вахтера.

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

Це я використовую для створення будь-яких компонентів введення даних, і це працює чудово. Будь-які нові дані, надіслані (v-модель (ed)) від батьків, будуть спостерігати за значенням спостерігача і призначаються вхідній змінній, і як тільки вхід буде отриманий, ми можемо зафіксувати цю дію і випромінювати вхід для батьків, припускаючи, що дані вводяться від елемента форми.


5

Я також зіткнувся з цим питанням. Попередження зникло після використання $onта $emit. Це щось на зразок використання $onта $emitрекомендується для надсилання даних від дочірнього компонента до батьківського компонента.


4

Якщо ви хочете мутувати реквізит - використовуйте об'єкт.

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

компонент:

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

Як і примітка, ви повинні бути обережними, коли мутуєте предмети. Об'єкти Javascript передаються за посиланням, але є застереження: Посилання порушується, коли ви встановлюєте змінну, рівну значенню.
ira

3

Вам потрібно додати такий обчислюваний метод

компонент.вью

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

3

односторонній Потік даних, згідно з https://vuejs.org/v2/guide/components.html , компонент слідує за одностороннім потоком даних, Усі реквізити утворюють одностороннє прив'язку між дочірнім властивістю та батьківським По-перше, коли оновлення батьківського властивості буде надходити до дитини, але не навпаки, це запобігає випадковому виправленню батьківських компонентів, що може ускладнити розуміння потоку даних вашого додатка.

Крім того, кожного разу, коли батьківський компонент оновлює всі реквізити дочірніх компонентів, буде оновлено останнім значенням. Це означає, що не слід намагатися мутувати опору всередині дочірнього компонента. Якщо ви зробите .vue попередить вас у консолі.

Зазвичай є два випадки, коли спокушати мутацію опори: опора використовується для передачі початкового значення; дочірній компонент хоче згодом використовувати його як локальну властивість даних. Реквізит передається як вихідне значення, яке потрібно перетворити. Правильна відповідь на ці випадки використання: Визначте локальну властивість даних, яка використовує початкове значення опори як початкове значення:

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

Визначте обчислюване властивість, яке обчислюється зі значення опори:

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

2

Реквізит Vue.js не слід мутувати, оскільки це вважається анти-шаблоном у Vue.

Підхід, який вам потрібно буде застосувати, - це створення властивості даних для вашого компонента, що посилається на оригінальну властивість списку опор

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

Тепер на вашу структуру списку, яка передається компоненту, посилається та мутується через dataвластивість вашого компонента :-)

Якщо ви хочете зробити більше, ніж просто проаналізувати властивість списку, тоді скористайтеся computedвластивістю компонента Vue . Це дозволяє вам зробити більш глибокі мутації до реквізиту.

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

Наведений вище приклад аналізує ваш список списку і відфільтровує його лише до активних тем -списків, реєструє його для шніттів і хихикає і повертає його.

Примітка : обидва data& computedвластивості посилаються в шаблоні однаково, наприклад,

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

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

Можна легко подумати, що computedвластивість (будучи методом) потрібно називати ... це не так


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

Можливо, це задовольнить ваші потреби.


2

Додаючи найкращу відповідь,

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

Налаштування реквізиту масивом призначено для розробки / складання прототипів, у виробництві переконайтесь, що встановіть типи опор ( https://vuejs.org/v2/guide/components-props.html ) та встановіть значення за замовчуванням у випадку, якщо опора не має була заселена батьком, як так.

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

Таким чином, ви отримуєте порожній об'єкт mutableListзамість помилки JSON.parse, якщо він не визначений.


0

Vue.js вважає це антитілом. Наприклад, оголошення та встановлення деяких реквізитів, як

this.propsVal = 'new Props Value'

Отже, щоб вирішити цю проблему, вам слід взяти значення від реквізиту до даних або обчисленої властивості екземпляра Vue, як це:

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

Це обов'язково спрацює.


0

Крім вищезазначеного, для інших, які мають таке питання:

"Якщо значення реквізиту не потрібно і, таким чином, не завжди повертається, передані дані повертаються undefined(замість порожніх)". Яке може заплутати <select>значення за замовчуванням, я вирішив його, перевіривши, чи встановлено значення beforeMount()(і встановити його, якщо ні) наступним чином:

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

Я хочу дати цю відповідь, яка допомагає уникнути використання великої кількості коду, спостерігачів та обчислених властивостей. У деяких випадках це може бути хорошим рішенням:

Реквізити призначені для забезпечення одностороннього спілкування.

Коли у вас є модальна show/hideкнопка з опорою, найкращим рішенням для мене є передача події:

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

Потім додайте слухача до модального елемента:

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

(У цьому випадку опора show, ймовірно, непотрібна, оскільки ви можете використовувати легкий v-if="show"безпосередньо на базовому модалі)


0

Я особисто пропоную, якщо вам потрібно вимкнути реквізит, спочатку передайте їх до обчисленої властивості та повертайтеся звідти, після цього ви можете легко вимкнути реквізит, навіть якщо ви можете відстежувати мутацію опори, якщо вони мутуються з іншого компонент теж або ми можемо також дивитися.


0

Оскільки реквізит Vue - це один із способів передачі даних, це запобігає випадковому виправленню стану батьківських компонентів.

З офіційного документа Vue ми знайдемо 2 способи вирішення цієї проблеми

  1. якщо дочірній компонент хоче використовувати реквізити як локальні дані, найкраще визначити локальну властивість даних.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. Реквізит передається як вихідне значення, яке потрібно перетворити. У цьому випадку найкраще визначити обчислену властивість, використовуючи значення опори:

      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

ТАК !, мутація атрибутів у vue2 є анти-шаблоном. АЛЕ ... Просто порушуйте правила, використовуючи інші правила, і йдіть вперед! Вам потрібно додати модифікатор .sync до атрибута компонента в області paret. <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡 Просто ми вбиваємо Бетмена 😁


0

Бо коли TypeScript є вашим кращим язиком. розвитку

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

і ні

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



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

0

Нижче наведено компонент снек-бару, коли я даю змінну snackbar безпосередньо у v-модель, як це, якщо буде працювати, але в консолі, вона дасть помилку, оскільки

Уникайте мутації опори безпосередньо, оскільки значення буде перезаписано кожного разу, коли батьківський компонент повторно надає. Натомість використовуйте дані або обчислені властивості на основі значення опори.

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

Правильний спосіб позбутися від цієї помилки мутації - це використання 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>

Отже, в основному компоненті, де ви будете завантажувати цю закусочну, ви можете просто зробити цей код

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

Це працювало для мене

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.