Skip to content

Propiedades Computadas

Ejemplo Básico

Las expresiones en las plantillas son muy convenientes, pero están pensadas para operaciones simples. Poner demasiada lógica en tus plantillas puede hacerlas infladas y difíciles de mantener. Por ejemplo, si tenemos un objeto con un array anidado:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Guía Avanzada',
          'Vue 3 - Guía Básica',
          'Vue 4 - El Misterio'
        ]
      }
    }
  }
}
js
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Guía Avanzada',
    'Vue 3 - Guía Básica',
    'Vue 4 - El Misterio'
  ]
})

Y queremos mostrar diferentes mensajes dependiendo de si author ya tiene algunos libros o no:

template
<p>Ha publicado libros:</p>
<span>{{ author.books.length > 0 ? 'Sí' : 'No' }}</span>

Llegados a este punto, la plantilla se está volviendo un poco desordenada. Tenemos que mirarla un segundo antes de darnos cuenta de que realiza un cálculo en función de author.books. Y lo que es más importante, probablemente no queramos repetirnos si necesitamos incluir este cálculo en la plantilla más de una vez.

Por eso, para una lógica compleja que incluya datos reactivos, se recomienda utilizar una propiedad computada. Aquí está el mismo ejemplo, refactorizado:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Guía Avanzada',
          'Vue 3 - Guía Básica',
          'Vue 4 - El Misterio'
        ]
      }
    }
  },
  computed: {
    // un getter computado
    publishedBooksMessage() {
      // `this` apunta a la instancia del componente
      return this.author.books.length > 0 ? 'Sí' : 'No'
    }
  }
}
template
<p>Ha publicado libros:</p>
<span>{{ publishedBooksMessage }}</span>

Pruébalo en la Zona de Práctica

Aquí hemos declarado una propiedad computada publishedBooksMessage.

Intenta cambiar el valor del array books en la aplicación data y verás cómo publishedBooksMessage cambia en consecuencia.

Puedes enlazar datos a las propiedades computadas en las plantillas como si se tratara de una propiedad normal. Vue es consciente de que this.publishedBooksMessage depende de this.author.books, por lo que actualizará cualquier enlace que dependa de this.publishedBooksMessage cuando cambie this.author.books.

Véase también: Escritura de Propiedades Computadas

vue
<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Guía Avanzada',
    'Vue 3 - Guía Básica',
    'Vue 4 - El Misterio'
  ]
})

// una ref computada
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Sí' : 'No'
})
</script>

<template>
  <p>Ha publicado libros:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

Pruébalo en la Zona de Práctica

Aquí hemos declarado la propiedad computada publishedBooksMessage. La función computed() espera que se le pase una función getter, y el valor devuelto es una ref computada. Al igual que con las refs normales, se puede acceder al resultado calculado como publishedBooksMessage.value. Las refs computadas también se desenvuelven automáticamente en las plantillas, por lo que se puede hacer referencia a ellas sin .value en las expresiones de las plantillas.

Una propiedad computada rastrea automáticamente sus dependencias reactivas. Vue es consciente de que el cálculo de publishedBooksMessage depende de author.books, por lo que actualizará cualquier enlace que dependa de publishedBooksMessage cuando cambie author.books.

Véase también Escritura de computed()

Almacenamiento en Caché Computado vs. Métodos

Te habrás dado cuenta de que podemos conseguir el mismo resultado invocando un método en la expresión:

template
<p>{{ calculateBooksMessage() }}</p>
js
// en un componente
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Sí' : 'No'
  }
}
js
// en un componente
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Sí' : 'No'
}

En lugar de una propiedad computada, podemos definir la misma función como un método. Para el resultado final, los dos enfoques son de hecho exactamente lo mismo. Sin embargo, la diferencia es que las propiedades computadas se almacenan en caché en función de sus dependencias reactivas. Una propiedad computada sólo se reevaluará cuando alguna de sus dependencias reactivas haya cambiado. Esto significa que mientras author.books no haya cambiado, un acceso múltiple a publishedBooksMessage devolverá inmediatamente el resultado calculado anteriormente sin tener que volver a ejecutar la función getter.

Esto también significa que la siguiente propiedad calculada nunca se actualizará, porque Date.now() no es una dependencia reactiva:

js
computed: {
  now() {
    return Date.now()
  }
}
js
const now = computed(() => Date.now())

En comparación, una invocación a un método ejecutará siempre la función cada vez que se produzca una nueva renderización.

¿Por qué necesitamos el almacenamiento en caché? Imagina que tenemos una costosa propiedad computada lista, que requiere recorrer un enorme array y hacer muchos cálculos. Luego podemos tener otras propiedades computadas que a su vez dependen de list. Sin la caché, estaríamos ejecutando el getter de list muchas más veces de las necesarias. En los casos en los que no quieras almacenar en caché, en su lugar utiliza una llamada a un método.

"Escribible" Computado

Las propiedades computadas son, por defecto, de tipo getter. Si intentas asignar un nuevo valor a una propiedad computada, recibirás una advertencia en tiempo de ejecución. En los raros casos en los que necesites una propiedad computada "escribible", puedes crear una proporcionando tanto un getter como un setter:

js
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        // Nota: aquí utilizamos la sintaxis de asignación de desestructuración.
        ;[this.firstName, this.lastName] = newValue.split(' ')
      }
    }
  }
}

Ahora cuando ejecutes this.fullName = 'John Doe', se invocará el setter y se actualizarán this.firstName y this.lastName en consecuencia.

vue
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // Nota: aquí utilizamos la sintaxis de asignación de desestructuración.
    ;[firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

Ahora cuando ejecutes fullName.value = 'John Doe', el setter será invocado y firstName y lastName serán actualizados en consecuencia.

Mejores Prácticas

Los getters deben estar libres de efectos secundarios

Es importante recordar que las funciones getter computadas sólo deben realizar cálculos puros y estar libres de efectos secundarios. Por ejemplo, ¡no mutes otros estados, no hagas peticiones asíncronas ni mutes el DOM dentro de un getter computado!. Piensa en una propiedad computada como una descripción declarativa de cómo derivar un valor basado en otros valores: su única responsabilidad debe ser calcular y devolver ese valor. Más adelante en la guía discutiremos cómo podemos realizar efectos secundarios en reacción a los cambios de estado con los watchers.

Evitar la mutación del valor computado

El valor devuelto de una propiedad computada es un estado derivado. Piensa en ello como una instantánea temporal: cada vez que el estado fuente cambia, se crea una nueva instantánea. No tiene sentido mutar una instantánea, por lo que un valor de retorno computado debe ser tratado como de sólo lectura y nunca ser mutado; en su lugar, actualizar el estado fuente del que depende para desencadenar nuevos cálculos.

Propiedades Computadas has loaded