Quelle est la bonne façon de passer des accessoires en tant que données initiales dans Vue.js 2?

97

Je veux donc passer des accessoires à un composant Vue, mais je m'attends à ce que ces accessoires changent à l'avenir de l'intérieur de ce composant, par exemple lorsque je mets à jour ce composant Vue de l'intérieur en utilisant AJAX. Donc, ils ne servent qu'à l'initialisation du composant.

Mon cars-listélément de composant Vue où je passe des accessoires avec des propriétés initiales à 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>

La façon dont je le fais en ce moment ce que l' intérieur de mon single-carélément j'assignant this.initialPropertiesà mon this.data.propertiessur le created()crochet d'initialisation. Et cela fonctionne et est réactif.

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

Mais mon problème avec cela est que je ne sais pas si c'est une bonne façon de le faire? Cela ne me causera-t-il pas des ennuis sur la route? Ou y a-t-il une meilleure façon de le faire?

Dominik Serafin
la source
13
C'est la chose la plus déroutante à propos de Vue à mon avis: tout dataest two-waylié, mais vous ne pouvez pas passer dataaux composants, vous passez props, mais vous ne pouvez pas changer le reçu propsni le convertir propsen data. Alors quoi? Une chose que j'ai apprise est que vous devez transmettre propset déclencher des événements. C'est-à-dire que si le composant souhaite modifier ce propsqu'il a reçu, il doit appeler un événement et être «rendu». Mais ensuite, il vous reste une one-wayliaison exactement comme React et je ne vois pas l'utilité pour cela data. Assez déroutant.
André Pena
1
Les données sont principalement destinées à l'usage privé du composant. Tout ce qui est placé dessus dans le contexte du composant est réactif et peut être lié. Le concept avec les accessoires est de transmettre des valeurs à un composant mais d'éviter que le composant puisse introduire silencieusement des changements d'état dans le parent en modifiant une valeur transmise. Il vaut mieux le rendre explicite dans un événement comme vous l'avez indiqué. C'était un changement de philosophie de Vue 1.0 à 2.0.
David K.Hess
Aujourd'hui, j'ai essayé de démarrer un fil de discussion ici: forum.vuejs.org/t/…
bgraves

Réponses:

103

Merci à ce https://github.com/vuejs/vuejs.org/pull/567 je connais la réponse maintenant.

Méthode 1

Passez l'accessoire initial directement aux données. Comme l'exemple dans les documents mis à jour:

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

Mais gardez à l'esprit que si le prop passé est un objet ou un tableau utilisé dans l'état du composant parent, toute modification de cet accessoire entraînera le changement de cet état du composant parent.

Attention : cette méthode n'est pas recommandée. Cela rendra vos composants imprévisibles. Si vous avez besoin de définir des données parentes à partir de composants enfants, utilisez la gestion d'état comme Vuex ou utilisez "v-model". https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components

Méthode 2

Si votre accessoire initial est un objet ou un tableau et si vous ne voulez pas que les modifications de l'état des enfants se propagent à l'état parent, utilisez simplement par exemple Vue.util.extend[1] pour faire une copie des accessoires au lieu de les pointer directement vers les données des enfants, comme ceci:

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

Méthode 3

Vous pouvez également utiliser l'opérateur de propagation pour cloner les accessoires. Plus de détails dans la réponse Igor: https://stackoverflow.com/a/51911118/3143704

Mais gardez à l'esprit que les opérateurs de diffusion ne sont pas pris en charge dans les anciens navigateurs et pour une meilleure compatibilité, vous devrez transpiler le code, par exemple en utilisant babel.

Notes de bas de page

[1] Gardez à l'esprit qu'il s'agit d'un utilitaire Vue interne et qu'il peut changer avec les nouvelles versions. Vous souhaiterez peut-être utiliser d'autres méthodes pour copier cet accessoire, voir " Comment cloner correctement un objet JavaScript? ".

Mon violon là où je le testais: https://jsfiddle.net/sm4kx7p9/3/

Dominik Serafin
la source
1
il est donc normal que les modifications soient propagées au composant parent?
igor
1
@igor personnellement, j'essaierais de l'éviter autant que vous le pouvez. Cela rendra vos composants imprévisibles. Je pense que la meilleure façon d'obtenir des changements dans le composant parent est d'utiliser la méthode "v-model" où vous émettez des changements de votre composant enfant au composant parent. vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Dominik Serafin
oui mais qu'en est-il des objets profonds? devrais-je copier en profondeur? dois-je repenser mes composants pour ne pas utiliser d'objet profond? c'est-à-dire un accessoire avec: { ok: 1, notOk: { meh: 2 } }s'il est défini sur une donnée interne avec l'une des méthodes de clonage précédentes, cela changerait toujours l'accessoirenotOk.meh
Nikso
1
Merci pour l'excellente question et réponse @DominikSerafin. Cela illustre parfaitement la guérison d'Achille de Vue.js. En tant que framework, il gère très bien la composante, mais le passage de données entre les compositions est un PITA majeur. Il suffit de regarder le cauchemar de nomenclature créé. Alors maintenant, j'ai besoin de préfixer tous mes accessoires initialjuste pour pouvoir les utiliser dans mon ordinateur. Ce serait tellement plus propre si nous pouvions simplement passer un accessoire appelé dataà n'importe quel ordinateur et le faire propager automatiquement les données localisées à l'intérieur sans toute cette initialPropNamefolie.
AJB
1
@DominikSerafin J'entends ce que vous dites et vous n'avez pas tort. Mais écrivez une application de création de formulaires en utilisant Vue.js et vous verrez rapidement ce que je veux dire. Je suis en train de changer ma technique de partage de données de prop --> comp.dataà utiliser Vue-Stash pour toutes les données (ou Vuex fonctionnerait aussi). Mais maintenant, je ne fais que rebondir mes données sur l' windowobjet, comme je le faisais avant que les frameworks JS «modernes» ne m'imposent leur opinion. Cela pose donc la question suivante: quel est l'intérêt de la propriété comp.data?
AJB
25

En complément de la réponse de @ dominik-serafin:

Si vous passez un objet, vous pouvez facilement le cloner à l'aide de l'opérateur de propagation (syntaxe ES6):

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

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

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

Mais le plus important est de ne pas oublier d'utiliser opt. 2 au cas où vous transmettriez une valeur calculée, ou plus qu'une valeur asynchrone. Sinon, la valeur locale ne sera pas mise à jour.

Démo:

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
la source
Hé @ igor-parra, il semble que vous ayez soumis des réponses en double. Et il pourrait également être bon de mentionner que l'opérateur de propagation n'est pas pris en charge dans tous les navigateurs et qu'il peut nécessiter une étape de transpilage pour une meilleure compatibilité. Sinon, ça a l'air génial - merci d'avoir ajouté cette méthode alternative!
Dominik Serafin
@ dominik-serafin Oui, vous avez raison, j'ai ajouté une référence à ES6. Dans les applications basées sur le Webpack, il est si facile d'avoir babel préconfiguré pour utiliser pleinement le javascript moderne.
Igor Parra
recordLocal: [...this.record](dans mon cas, l'enregistrement est un tableau) ne fonctionnait pas pour moi - après le chargement et l'inspection du composant vue, recordcontient les éléments et recordLocalétait un tableau vide.
Christian
Quand je l'utilise avec la propriété COMPUTED, cela se passe bien, pour moi, lorsque j'essaie de définir à l'intérieur des données, cela ne fonctionne pas = (... Mais je l'ai obtenu en utilisantcomputed
felipe muner
Sois prudent. L'opérateur de propagation ne fait que des clones superficiels, donc pour les objets qui contiennent des objets ou des tableaux, vous copiez toujours des pointeurs au lieu d'obtenir une nouvelle copie. J'utilise cloneDeep de lodash.
Cindy Conway le
13

Je pense que vous le faites bien parce que c'est ce qui est indiqué dans la documentation.

Définir une propriété de données locales qui utilise la valeur initiale de l'accessoire comme valeur initiale

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

jonan.pineda
la source
4
Pour les accessoires pass-par-valeur, c'est bien. Dans ce cas, c'est un objet, donc le changer affectera le parent. À partir de cette page: "Notez que les objets et les tableaux en JavaScript sont passés par référence, donc si le prop est un tableau ou un objet, la mutation de l'objet ou du tableau lui-même à l'intérieur du composant enfant affectera l'état parent."
Jake
Cela fonctionne pour moi et est clairement documenté. cette ligne dans la réponse ci-dessus entre en conflit avec la documentation vue pour autant que je sache "Attention: cette méthode n'est pas recommandée. Cela rendra vos composants imprévisibles. Si vous avez besoin de définir des données parents à partir de composants enfants, utilisez la gestion d'état comme Vuex ou utilisez "v-model". "
Nathaniel Rink
Donc, après 4 ans et des centaines d'heures d'apprentissage, je suis de retour là où j'ai commencé à faire rebondir les vars sur l' windowobjet.
AJB