Vue 2 - Adereços mutantes vue-warn


180

Comecei a série https://laracasts.com/series/learning-vue-step-by-step . Parei na lição Vue, Laravel e AJAX com este erro:

vue.js: 2574 [Vue warn]: Evite alterar um objeto diretamente, pois o valor será substituído sempre que o componente pai for renderizado novamente. Em vez disso, use dados ou propriedades calculadas com base no valor do suporte. Suporte sendo mutado: "lista" (encontrada no componente)

Eu tenho esse código no main.js

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

Sei que o problema está em created () quando sobrescrevo o suporte da lista, mas sou novato no Vue, portanto, não sei como corrigi-lo. Alguém tem uma idéia de como (e por favor explique o porquê) para corrigi-lo?


2
Acho que é apenas uma mensagem de aviso e não um erro.
David R

Respostas:


264

Isso tem a ver com o fato de que a mutação de um suporte local é considerada um anti-padrão no Vue 2

O que você deve fazer agora, caso queira alterar um suporte localmente , é declarar um campo no seu dataque use o propsvalor como seu valor inicial e depois modifique a cópia:

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

Você pode ler mais sobre isso no guia oficial do Vue.js.


Nota 1: Observe que você não deve usar o mesmo nome para seu propedata , por exemplo:

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

Nota 2: Uma vez que eu sinto que há alguma confusão a respeito propse reatividade , eu sugiro que você ter um olhar sobre este tópico


3
Essa é uma ótima resposta, mas também acho que ela deve ser atualizada para incluir a associação de eventos. Um evento acionado pelo filho pode atualizar o pai, que passará adereços atualizados para o filho. Dessa forma, a fonte da verdade ainda é mantida em todos os componentes. Também vale a pena mencionar o Vuex para gerenciar estados compartilhados entre vários componentes.
Wes Harper

@WesHarper, também é possível usar o modificador .sync como uma abreviação para obter automaticamente o evento de atualização dos pais.
danb4r 30/04

48

O padrão Vue está propsbaixo e eventsativo. Parece simples, mas é fácil esquecer ao escrever um componente personalizado.

A partir do Vue 2.2.0, você pode usar o modelo v (com propriedades calculadas ). Eu descobri que essa combinação cria uma interface simples, limpa e consistente entre os componentes:

  • Qualquer propspassagem para seu componente permanece reativa (ou seja, não é clonada nem requer uma watchfunção para atualizar uma cópia local quando são detectadas alterações).
  • As alterações são emitidas automaticamente para o pai.
  • Pode ser usado com vários níveis de componentes.

Uma propriedade computada permite que o setter e o getter sejam definidos separadamente. Isso permite que o Taskcomponente seja reescrito da seguinte maneira:

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

A propriedade model define a qual propestá associado v-modele qual evento será emitido nas alterações. Você pode chamar esse componente do pai da seguinte maneira:

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

A listLocalpropriedade computada fornece uma interface simples de getter e setter dentro do componente (pense nisso como uma variável privada). Dentro de #task-templatevocê pode renderizar listLocale permanecerá reativo (ou seja, se houver parentListalterações, ele atualizará o Taskcomponente). Você também pode fazer uma mutação listLocalchamando o setter (por exemplo, this.listLocal = newList) e ele emitirá a alteração para o pai.

O melhor desse padrão é que você pode passar listLocalpara um componente filho de Task(using v-model), e as alterações do componente filho serão propagadas para o componente de nível superior.

Por exemplo, digamos que temos um EditTaskcomponente separado para fazer algum tipo de modificação nos dados da tarefa. Usando o mesmo v-modelpadrão de propriedades e computado, podemos passar listLocalpara o componente (using v-model):

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

Se EditTaskemite um mudança que vai chamar apropriadamente set()em listLocale, assim, propagar o evento para o nível superior. Da mesma forma, o EditTaskcomponente também pode chamar outros componentes filhos (como elementos de formulário) usando v-model.


1
Estou fazendo algo semelhante, exceto com sincronização. O problema é que preciso executar outro método depois de emitir meu evento de alteração, no entanto, quando o método é executado e atinge o getter, recebo o valor antigo porque o evento de alteração ainda não foi captado pelo ouvinte / propagado de volta ao filho. Este é um caso em que desejo alterar o suporte para que meus dados locais estejam corretos até que a atualização pai seja propagada novamente. Quaisquer pensamentos a esse respeito?
precisa saber é o seguinte

Eu suspeito que chamar o getter do setter não é uma boa prática. O que você pode considerar é vigiar sua propriedade computada e adicionar sua lógica lá.
Chris

adereços e eventos para cima é o que clicou comigo. Onde isso é explicado nos documentos dos VueJs?
usar o seguinte


Eu acho que essa é a maneira mais simples de emitir as alterações no componente pai.
Muhammad

36

O Vue apenas avisa: você altera o suporte no componente, mas quando o componente pai é renderizado novamente, a "lista" será substituída e você perde todas as alterações. Portanto, é perigoso fazê-lo.

Use a propriedade computada como esta:

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

25
Que tal um suporte de ligação bidirecional?
21417 Josh R.

7
Se você deseja uma ligação de dados bidirecional, use eventos personalizados. Para obter mais informações, leia: vuejs.org/v2/guide/components.html#sync-Modifier Você ainda não pode modificar diretamente o suporte, precisa de um evento e função que lida com uma alteração de suporte para você.
Marco

22

Se você estiver usando o Lodash, poderá clonar o suporte antes de devolvê-lo. Esse padrão é útil se você modificar esse suporte no pai e no filho.

Digamos que tenhamos uma lista de props na grade de componentes .

No componente pai

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

No componente filho

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

19

Props para baixo, eventos para cima. Esse é o padrão de Vue. O ponto é que, se você tentar alterar os adereços que passam de um pai ou mãe. Não funcionará e apenas será sobrescrito repetidamente pelo componente pai. O componente filho pode emitir apenas um evento para notificar o componente pai a executar sth. Se você não gostar dessas restrições, poderá usar o VUEX (na verdade, esse padrão será uma sucção na estrutura de componentes complexos, você deve usar o VUEX!)


2
Também há uma opção para usar o barramento de eventos
Steven Pribilinskiy

o barramento de eventos não é suportado nativamente no vue 3. github.com/vuejs/rfcs/pull/118
AlexMA

15

Você não deve alterar o valor dos objetos no componente filho. Se você realmente precisar mudar, pode usar .sync. Bem assim

<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

De acordo com o VueJs 2.0, você não deve alterar um suporte dentro do componente. Eles são apenas mutados pelos pais. Portanto, você deve definir variáveis ​​nos dados com nomes diferentes e mantê-las atualizadas, observando os objetos reais. Caso o objeto da lista seja alterado por um pai, você pode analisá-lo e atribuí-lo a mutableList. Aqui está uma solução completa.

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

Ele usa mutableList para renderizar seu modelo, assim você mantém a sua lista segura no componente.


não é assistir caro para ser usado?
Kick Buttowski

6

não altere os adereços diretamente em components.if você precisa alterá-lo, defina uma nova propriedade como esta:

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

E altere o valor de listClone.


6

A resposta é simples: você deve interromper a mutação direta do suporte atribuindo o valor a algumas variáveis ​​locais do componente (pode ser uma propriedade de dados, calculada com getters, setters ou observadores).

Aqui está uma solução simples usando o observador.

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

É o que eu uso para criar qualquer componente de entrada de dados e funciona muito bem. Quaisquer novos dados enviados (modelo-v) do pai serão observados pelo observador de valores e serão atribuídos à variável de entrada e, uma vez que a entrada seja recebida, podemos capturar essa ação e emitir entrada para o pai, sugerindo que os dados sejam inseridos. do elemento do formulário.


5

Eu também enfrentei esse problema. O aviso foi depois que eu uso $one $emit. É algo como usar $one $emitrecomendado para enviar dados do componente filho para o componente pai.


4

Se você deseja alterar os adereços - use object.

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

componente:

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

Apenas como uma nota, você deve ter cuidado ao alterar objetos. Os objetos Javascript são passados ​​por referência, mas há uma ressalva: A referência é interrompida quando você define uma variável igual a um valor.
ira

3

Você precisa adicionar um método computado como este

component.vue

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

3

fluxo de dados unidirecional, de acordo com https://vuejs.org/v2/guide/components.html , o componente segue o fluxo de dados unidirecional . Todos os adereços formam uma ligação unidirecional entre a propriedade filho e o pai primeiro, quando a propriedade pai é atualizada, ela flui para o filho, mas não o contrário, isso impede que os componentes filhos modifiquem acidentalmente os pais, o que pode dificultar a compreensão dos dados do aplicativo.

Além disso, sempre que o componente pai for atualizado, todos os objetos nos componentes filhos serão atualizados com o valor mais recente. Isso significa que você não deve tentar alterar um suporte dentro de um componente filho. Se você fizer .vue, você será avisado no console.

Geralmente, existem dois casos em que é tentador alterar um suporte: o suporte é usado para transmitir um valor inicial; o componente filho deseja usá-lo como uma propriedade de dados local posteriormente. O suporte é passado como um valor bruto que precisa ser transformado. A resposta apropriada para esses casos de uso são: Defina uma propriedade de dados local que use o valor inicial do objeto como seu valor inicial:

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

Defina uma propriedade calculada que é calculada a partir do valor do suporte:

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

2

Os adereços do Vue.js não devem ser alterados, pois isso é considerado um antipadrão no Vue.

A abordagem que você precisará adotar é criar uma propriedade de dados em seu componente que faça referência à propriedade prop original da lista

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

Agora sua estrutura de lista que é passada para o componente é referenciada e alterada através da datapropriedade do seu componente :-)

Se você deseja fazer mais do que apenas analisar a propriedade da lista, use a computedpropriedade do componente Vue . Isso permite que você faça mutações mais profundas em seus objetos.

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

O exemplo acima analisa o suporte da sua lista e o filtra para apenas os sistemas de lista ativos , faz o logout de schnitts e risadinhas e o retorna.

nota : ambas as propriedades datae computedsão referenciadas no modelo da mesma forma, por exemplo

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

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

Pode ser fácil pensar que uma computedpropriedade (sendo um método) precisa ser chamada ... nã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'
})

Talvez isso atenda às suas necessidades.


2

Adicionando a melhor resposta,

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

A configuração de props por uma matriz é destinada à dev / prototipagem. Na produção, defina os tipos de prop ( https://vuejs.org/v2/guide/components-props.html ) e defina um valor padrão caso a prop não tenha foi preenchido pelo pai, como tal.

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

Dessa forma, você obtém pelo menos um objeto vazio em mutableListvez de um erro JSON.parse, se não estiver definido.


0

O Vue.js considera isso um antipadrão. Por exemplo, declarar e definir alguns adereços como

this.propsVal = 'new Props Value'

Portanto, para resolver esse problema, é necessário levar um valor dos adereços para os dados ou a propriedade calculada de uma instância do Vue, assim:

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

Isso definitivamente vai funcionar.


0

Além do acima, para outras pessoas com o seguinte problema:

"Se o valor de props não for necessário e, portanto, nem sempre retornado, os dados passados ​​retornarão undefined(em vez de vazios)". O que poderia atrapalhar <select>o valor padrão, resolvi-o verificando se o valor está definido beforeMount()(e, se não estiver), da seguinte forma:

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

Quero dar essa resposta que ajuda a evitar o uso de muito código, observadores e propriedades computadas. Em alguns casos, isso pode ser uma boa solução:

Os acessórios são projetados para fornecer comunicação unidirecional.

Quando você tem um show/hidebotão modal com um suporte, a melhor solução para mim é emitir um evento:

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

Em seguida, adicione listener ao elemento modal:

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

(Nesse caso, o suporte showprovavelmente não é necessário porque você pode usar um easy v-if="show"diretamente no modal base)


0

Pessoalmente, sempre sugiro que você precise alterar os adereços, primeiro passe-os para a propriedade computada e retorne a partir daí; depois, é possível alterar os adereços facilmente, mesmo que você possa rastrear a mutação dos adereços, se estes estiverem sendo alterados por outro componente também ou podemos assistir também.


0

Como os adereços do Vue são um caminho de fluxo de dados, isso evita que os componentes filhos modifiquem acidentalmente o estado dos pais.

No documento oficial do Vue, encontraremos 2 maneiras de resolver esses problemas

  1. se o componente filho quiser usar adereços como dados locais, é melhor definir uma propriedade de dados local.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. O suporte é passado como um valor bruto que precisa ser transformado. Nesse caso, é melhor definir uma propriedade computada usando o valor do 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

SIM !, os atributos mutantes no vue2 são antipadrão. MAS ... Apenas quebre as regras usando outras regras e siga em frente! O que você precisa é adicionar o modificador .sync ao atributo do componente no escopo do paret. <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡 É simples matar o batman 😁


0

Para quando o TypeScript é o seu idioma preferido. de desenvolvimento

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

e não

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



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

0

Abaixo está um componente da lanchonete, quando eu fornecer a variável snackbar diretamente no modelo v como este, se funcionar, mas no console, ocorrerá um erro como

Evite alterar um suporte diretamente, pois o valor será substituído sempre que o componente pai for renderizado novamente. Em vez disso, use dados ou propriedades calculadas com base no valor do suporte.

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

A maneira correta de se livrar desse erro de mutação é usar o 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>

Portanto, no componente principal onde você carregará essa lanchonete, basta fazer esse código

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

Isso funcionou para mim

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.