¿Cuál es la forma correcta de pasar props como datos iniciales en Vue.js 2?

97

Así que quiero pasar accesorios a un componente de Vue, pero espero que estos accesorios cambien en el futuro desde dentro de ese componente, por ejemplo, cuando actualizo ese componente de Vue desde adentro usando AJAX. Por lo que son solo para la inicialización del componente.

Mi cars-listelemento de componente Vue donde paso accesorios con propiedades iniciales a 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 forma en que lo hago en este momento es que dentro de mi single-carcomponente estoy asignando this.initialPropertiesa mi this.data.propertiesen el created()gancho de inicialización. Y funciona y es reactivo.

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

Pero mi problema con eso es que no sé si esa es la forma correcta de hacerlo. ¿No me causará algunos problemas en el camino? ¿O hay una mejor manera de hacerlo?

Dominik Serafín
fuente
13
En mi opinión, esto es lo más confuso de Vue: Every dataestá two-wayvinculado, pero no puede pasar dataa componentes, pasa props, pero no puede cambiar el recibido propsni convertir el propsa data. ¿Y que? Una cosa que aprendí es que debes transmitir propsy desencadenar eventos. Es decir, si el componente quiere cambiar el propsque recibió, debe llamar a un evento y ser "re-renderizado". Pero luego te quedas con un one-wayenlace exactamente como React y no veo el uso para dataentonces. Bastante confuso.
André Pena
1
Los datos están destinados principalmente al uso privado del componente. Todo lo que se coloca en él dentro del contexto del componente es reactivo y se puede vincular a. El concepto con props es pasar valores a un componente pero evitar que el componente pueda introducir silenciosamente cambios de estado en el padre cambiando un valor pasado. Es mejor hacerlo explícito en un evento como lo indicaste. Este fue un cambio de filosofía de Vue 1.0 a 2.0.
David K. Hess
Hoy he intentado iniciar un hilo aquí: forum.vuejs.org/t/…
bgraves

Respuestas:

103

Gracias a esto https://github.com/vuejs/vuejs.org/pull/567 ahora sé la respuesta.

Método 1

Pase el apoyo inicial directamente a los datos. Como el ejemplo en documentos actualizados:

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

Pero tenga en cuenta que si la propiedad pasada es un objeto o matriz que se usa en el estado del componente principal, cualquier modificación a esa propiedad dará como resultado el cambio en el estado de ese componente principal.

Advertencia : este método no se recomienda. Hará que sus componentes sean impredecibles. Si necesita configurar los datos principales de los componentes secundarios, use la administración de estado como Vuex o use "v-model". https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components

Método 2

Si su accesorio inicial es un objeto o matriz y si no desea que los cambios en el estado de los niños se propaguen al estado principal, simplemente use, por ejemplo, Vue.util.extend[1] para hacer una copia de los accesorios en lugar de apuntarlo directamente a los datos de los niños, así:

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

Método 3

También puede usar el operador de propagación para clonar los accesorios. Más detalles en la respuesta de Igor: https://stackoverflow.com/a/51911118/3143704

Pero tenga en cuenta que los operadores de propagación no son compatibles con los navegadores más antiguos y, para una mejor compatibilidad, deberá transpilar el código, por ejemplo, usando babel.

Notas al pie

[1] Tenga en cuenta que esta es una utilidad interna de Vue y puede cambiar con nuevas versiones. Es posible que desee utilizar otros métodos para copiar ese accesorio, consulte " ¿Cómo clono correctamente un objeto JavaScript? ".

Mi violín donde lo estaba probando: https://jsfiddle.net/sm4kx7p9/3/

Dominik Serafín
fuente
1
¿Entonces es una buena práctica que los cambios se propaguen al componente principal?
igor
1
@igor personalmente, trataría de evitarlo tanto como puedas. Hará que sus componentes sean impredecibles. Creo que la mejor manera de obtener cambios en el componente principal es usar el método "v-model" donde $ emite cambios desde su componente secundario al componente principal. vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Dominik Serafin
sí, pero ¿qué pasa con los objetos profundos? ¿Debo hacer una copia profunda? ¿Debo rediseñar mis componentes para no usar objetos profundos? es decir, un accesorio con: { ok: 1, notOk: { meh: 2 } }si se establece en un dato interno con cualquiera de los métodos de clonación anteriores, todavía cambiaría el accesorionotOk.meh
Nikso
1
Gracias por la excelente pregunta y respuesta @DominikSerafin. Esto ilustra perfectamente la curación de Aquiles de Vue.js. Como marco, maneja muy bien la creación de componentes, pero pasar datos entre las composiciones es un PITA importante. Basta con mirar la pesadilla de nomenclatura creada. Así que ahora necesito prefijar todos mis accesorios con initialsolo poder usarlos dentro de mi composición. Sería mucho más limpio si pudiéramos pasar un prop llamado dataa cualquier compilación y hacer que propague automáticamente los datos localizados sin toda esta initialPropNamelocura.
AJB
1
@DominikSerafin Escucho lo que estás diciendo y no te equivocas. Pero escriba una aplicación de creación de formularios con Vue.js y verá rápidamente a qué me refiero. De hecho, estoy en el proceso de cambiar mi técnica de compartir datos prop --> comp.datapara usar Vue-Stash para todos los datos (o Vuex también funcionaría). Pero ahora estoy rebotando mis datos del windowobjeto como solía hacer antes de que los marcos JS "modernos" me impusieran su opinión. Entonces surge la pregunta: ¿Cuál es el objetivo de la propiedad comp.data?
AJB
25

En compañía de la respuesta de @ dominik-serafin:

En caso de que esté pasando un objeto, puede clonarlo fácilmente usando el operador de extensión (sintaxis ES6):

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

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

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

Pero lo más importante es recordar utilizar opt. 2 en caso de que esté pasando un valor calculado, o más que un valor asincrónico. De lo contrario, el valor local no se actualizará.

Manifestación:

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
fuente
Hola @ igor-parra, parece que has enviado respuestas duplicadas. Y también podría ser bueno mencionar que el operador de propagación no es compatible con todos los navegadores y podría necesitar un paso de transpilación para una mejor compatibilidad. De lo contrario, se verá genial, ¡gracias por agregar este método alternativo!
Dominik Serafin
@ dominik-serafin Sí, tienes razón, agregué una referencia a ES6. En aplicaciones basadas en paquetes web, es muy fácil tener babel preconfigurado para hacer un uso completo del javascript moderno.
Igor Parra
recordLocal: [...this.record](en mi caso, el registro es una matriz) no funcionó para mí: después de cargar e inspeccionar el componente vue, recordcontiene los elementos y recordLocalera una matriz vacía.
Christian
Cuando lo uso a través de la propiedad COMPUTED, va bien, para mí, cuando trato de configurar dentro de los datos, no funciona = (... Pero lo obtuve usandocomputed
felipe muner
Ten cuidado. El operador de propagación solo clones superficiales, por lo que para los objetos que contienen objetos o matrices, seguirá copiando punteros en lugar de obtener una nueva copia. Yo uso cloneDeep de lodash.
Cindy Conway
13

Creo que lo está haciendo bien porque es lo que se indica en los documentos.

Defina una propiedad de datos local que use el valor inicial de la propiedad como su valor inicial

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

jonan.pineda
fuente
4
Para accesorios de paso por valor, está bien. En este caso, es un objeto, por lo que cambiarlo afectará al padre. Desde esa página: "Tenga en cuenta que los objetos y matrices en JavaScript se pasan por referencia, por lo que si la propiedad es una matriz u objeto, la mutación del objeto o matriz dentro del componente secundario afectará al estado principal".
Jake
Esto funciona para mí y está claramente documentado. esta línea en la respuesta anterior entra en conflicto con la documentación de vue por lo que puedo decir "Advertencia: este método no se recomienda. Hará que sus componentes sean impredecibles. Si necesita establecer datos principales de componentes secundarios, use la administración de estado como Vuex o use "v-model". "
Nathaniel Rink
Entonces, después de 4 años y cientos de horas de curva de aprendizaje, estoy de vuelta donde comencé a rebotar vars del windowobjeto.
AJB