Qual é a maneira correta de passar adereços como dados iniciais no Vue.js 2?

97

Então, eu quero passar adereços para um componente Vue, mas espero que eles mudem no futuro de dentro desse componente, por exemplo, quando eu atualizar esse componente Vue de dentro usando AJAX. Portanto, eles são apenas para inicialização do componente.

cars-listElemento de componente My Vue onde passo adereços com propriedades iniciais para single-car:

// cars-list.vue

<script>
    export default {
        data: function() {
            return {
                cars: [
                    {
                        color: 'red',
                        maxSpeed: 200,
                    },
                    {
                        color: 'blue',
                        maxSpeed: 195,
                    },
                ]
            }
        },
    }
</script>

<template>
    <div>
        <template v-for="car in cars">
            <single-car :initial-properties="car"></single-car>
        </template>
    </div>
</template>

A maneira que eu fazê-lo agora que dentro do meu single-carcomponente estou atribuindo this.initialPropertiesao meu this.data.propertiesno created()gancho de inicialização. E funciona e é reativo.

// single-car.vue

<script>
    export default {
        data: function() {
            return {
                properties: {},
            }
        },
        created: function(){
            this.data.properties = this.initialProperties;
        },
    }
</script>

<template>
    <div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>

Mas meu problema com isso é que não sei se essa é uma maneira correta de fazer isso. Isso não vai me causar alguns problemas ao longo da estrada? Ou existe uma maneira melhor de fazer isso?

Dominik Serafin
fonte
13
Esta é a coisa mais confusa sobre o Vue na minha opinião: Tudo dataestá two-wayvinculado, mas você não pode passar datapara componentes, você passa props, mas não pode alterar o recebido propsnem converter propspara data. Então o que? Uma coisa que aprendi é que você deve transmitir propse disparar eventos. Ou seja, se o componente quiser alterar o propsrecebido, deve chamar um evento e ser "renderizado". Mas então você fica com uma one-wayligação exatamente como React e não vejo utilidade para dataela. Muito confuso.
André Pena
1
Os dados destinam-se principalmente ao uso privado do componente. Tudo colocado nele dentro do contexto do componente é reativo e pode ser vinculado. O conceito com props é passar valores para um componente, mas evitar que o componente seja capaz de introduzir silenciosamente mudanças de estado no pai alterando um valor passado. É melhor torná-lo explícito em um evento como você indicou. Esta foi uma mudança de filosofia de Vue 1.0 para 2.0.
David K. Hess
Hoje tentei iniciar um tópico aqui: forum.vuejs.org/t/…
bgraves

Respostas:

102

Graças a https://github.com/vuejs/vuejs.org/pull/567 eu sei a resposta agora.

Método 1

Passe a proposta inicial diretamente para os dados. Como o exemplo em documentos atualizados:

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

Mas tenha em mente que se o prop passado for um objeto ou array usado no estado do componente pai, qualquer modificação nesse prop resultará na mudança no estado do componente pai.

Aviso : este método não é recomendado. Isso tornará seus componentes imprevisíveis. Se você precisar definir dados pai de componentes filhos, use o gerenciamento de estado como Vuex ou use o "modelo v". https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components

Método 2

Se o seu prop inicial é um objeto ou matriz e se você não quer que as mudanças no estado filho se propaguem para o estado pai, então use, por exemplo, Vue.util.extend[1] para fazer uma cópia dos props, em vez de apontá-lo diretamente para os dados dos filhos, como este:

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

Método 3

Você também pode usar o operador de propagação para clonar os adereços. Mais detalhes na resposta do Igor: https://stackoverflow.com/a/51911118/3143704

Mas ter em mente que os operadores de spread não são suportados em navegadores mais antigos e para melhor compatibilidade você precisa transpile o código de exemplo usando babel.

Notas de rodapé

[1] Tenha em mente que este é um utilitário interno do Vue e pode mudar com novas versões. Você pode usar outros métodos para copiar esse objeto , consulte " Como faço para clonar corretamente um objeto JavaScript? ".

Meu violino onde estava testando: https://jsfiddle.net/sm4kx7p9/3/

Dominik Serafin
fonte
1
então é uma prática normal que as alterações sejam propagadas para o componente pai?
igor
1
@igor pessoalmente, eu tentaria evitá-lo tanto quanto você pudesse. Isso tornará seus componentes imprevisíveis. Acho que a melhor maneira de obter mudanças no componente pai é usar o método "v-model", onde você emite mudanças de seu componente filho para o componente pai. vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Dominik Serafin
sim, mas e os objetos profundos? devo copiar em profundidade? devo reprojetar meus componentes para não usar objetos profundos? ou seja, um suporte com: { ok: 1, notOk: { meh: 2 } }se definido como um dado interno com qualquer um dos métodos de clonagem anterior, ainda mudaria o suportenotOk.meh
Nikso
1
Obrigado pela excelente pergunta e resposta @DominikSerafin. Isso ilustra perfeitamente a cura de Aquiles de Vue.js. Como uma estrutura, ele lida muito bem com a componentização, mas passar dados entre os comps é um grande PITA. Basta olhar para o pesadelo de nomenclatura criado. Portanto, agora eu preciso prefixar todos os meus adereços initialapenas para poder usá-los dentro do meu comp. Seria muito mais limpo se pudéssemos apenas passar um prop chamado datapara qualquer comp e fazer com que ele propagasse automaticamente os dados localizados sem toda essa initialPropNameloucura.
AJB de
1
@DominikSerafin Eu ouço o que você está dizendo, e você não está errado. Mas escreva um aplicativo criador de formulários usando Vue.js e você verá rapidamente o que quero dizer. Na verdade, estou no processo de mudar minha técnica de compartilhamento de dados prop --> comp.datapara usar o Vue-Stash para todos os dados (ou o Vuex também funcionaria). Mas agora estou apenas devolvendo meus dados do windowobjeto, como costumava fazer antes que os frameworks JS "modernos" impusessem sua opinião sobre mim. Portanto, levanta a questão: Qual é o objetivo da propriedade comp.data afinal?
AJB de
24

Acompanhando a resposta de @domik-serafin:

Caso você esteja passando um objeto, você pode cloná-lo facilmente usando o operador spread (sintaxe ES6):

 props: {
   record: {
     type: Object,
     required: true
   }
 },

data () { // opt. 1
  return {
    recordLocal: {...this.record}
  }
},

computed: { // opt. 2
  recordLocal () {
    return {...this.record}
  }
},

Mas o mais importante é lembrar de usar opt. 2 caso você esteja passando um valor calculado, ou mais do que um valor assíncrono. Caso contrário, o valor local não será atualizado.

Demo:

Vue.component('card', { 
  template: '#app2',
  props: {
    test1: null,
    test2: null
  },
  data () { // opt. 1
    return {
      test1AsData: {...this.test1}
    }
  },
  computed: { // opt. 2
    test2AsComputed () {
      return {...this.test2}
    }
  }
})

new Vue({
  el: "#app1",
  data () {
    return {
      test1: {1: 'will not update'},
      test2: {2: 'will update after 1 second'}
    }
  },
  mounted () {
    setTimeout(() => {
      this.test1 = {1: 'updated!'}
      this.test2 = {2: 'updated!'}
    }, 1000)
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

<div id="app1">
  <card :test1="test1" :test2="test2"></card>
</div>

<template id="app2">
  <div>
    test1 as data: {{test1AsData}}
    <hr />
    test2 as computed: {{test2AsComputed}}
  </div>
</template>

https://jsfiddle.net/nomikos3/eywraw8t/281070/

Igor Parra
fonte
Ei @ igor-parra, parece que você enviou respostas duplicadas. E também pode ser bom mencionar que o operador de propagação não é compatível com todos os navegadores e pode precisar da etapa de transpilação para uma melhor compatibilidade. Caso contrário, parece ótimo - obrigado por adicionar este método alternativo!
Dominik Serafin de
@ dominik-serafin Sim, você está certo, adicionei uma referência ao ES6. Em aplicações baseadas em webpack, é muito fácil ter o babel pré-configurado para fazer pleno uso do javascript moderno.
Igor Parra de
recordLocal: [...this.record](no meu caso, o registro é uma matriz) não funcionou para mim - depois de carregar e inspecionar o componente vue, recordcontém os itens e recordLocalera uma matriz vazia.
Cristão
Quando eu uso através da propriedade COMPUTED ele vai bem, pra mim quando tento setar dentro dos dados não está funcionando = (... Mas consegui usandocomputed
felipe muner
Seja cuidadoso. O operador spread apenas clones superficiais, portanto, para objetos que contêm objetos ou matrizes, você ainda copiará ponteiros em vez de obter uma nova cópia. Eu uso cloneDeep do lodash.
Cindy Conway
13

Eu acredito que você está fazendo certo porque é o que está declarado nos documentos.

Defina uma propriedade de dados local que usa o valor inicial do prop como seu valor inicial

https://vuejs.org/guide/components.html#One-Way-Data-Flow

Jonan.pineda
fonte
4
Para adereços passados ​​por valor, isso é bom. Nesse caso, é um objeto, portanto, alterá-lo afetará o pai. A partir dessa página: "Observe que os objetos e arrays em JavaScript são passados ​​por referência, portanto, se o prop for um array ou objeto, a mutação do próprio objeto ou array dentro do componente filho afetará o estado pai."
Jake
Isso funciona para mim e está claramente documentado. esta linha na resposta acima está em conflito com a documentação vue, tanto quanto eu posso dizer "Aviso: este método não é recomendado. Ele tornará seus componentes imprevisíveis. Se você precisar definir dados pai de componentes filhos, use gerenciamento de estado como Vuex ou use "v-model". "
Nathaniel Rink
Então, depois de 4 anos e centenas de horas de curva de aprendizado, estou de volta ao ponto em que comecei a jogar vars fora do windowobjeto.
AJB