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 template 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 template 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 templates 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.

Mira también: Escribir 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 computado como publishedBooksMessage.value. Las refs computadas también se desempaquetan automáticamente en las templates, por lo que se puede hacer referencia a ellas sin .value en las expresiones de las templates.

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

Véase también: Escribir Propiedades Computadas

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 solamente. 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í estamos usando 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 en consecuencia se actualizarán this.firstName y this.lastName.

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í estamos usando la sintaxis de asignación de desestructuración.
    ;[firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

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

Obtener el Valor Previo

  • Sólo soportado en 3.4+

En caso de que lo necesites, puedes obtener el valor previo devuelto por la propiedad computada accediendo al primer argumento del getter:

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    // Este cálculo devolverá el valor de count cuando sea menor o igual a 3.
    // Cuando count sea >=4, se devolverá el último valor que cumplió nuestra condición
    // en su lugar hasta que count sea menor o igual a 3
    alwaysSmall(previous) {
      if (this.count <= 3) {
        return this.count
      }

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

const count = ref(2)

// Este cálculo devolverá el valor de count cuando sea menor o igual a 3.
// Cuando count sea >=4, se devolverá el último valor que cumplió nuestra condición
// en su lugar hasta que count sea menor o igual a 3
const alwaysSmall = computed((previous) => {
  if (count.value <= 3) {
    return count.value
  }

  return previous
})
</script>

En caso de que estés usando un "escribible" computado:

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    alwaysSmall: {
      get(previous) {
        if (this.count <= 3) {
          return this.count
        }

        return previous
      },
      set(newValue) {
        this.count = newValue * 2
      }
    }
  }
}
vue
<script setup>
import { ref, computed } from 'vue'

const count = ref(2)

const alwaysSmall = computed({
  get(previous) {
    if (count.value <= 3) {
      return count.value
    }

    return previous
  },
  set(newValue) {
    count.value = newValue * 2
  }
})
</script>

Mejores Prácticas

Los getters deberían estar libres de efectos secundarios

Es importante recordar que las funciones getter computadas sólo deberían 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 debería ser calcular y retornar ese valor. Más adelante en la guía discutiremos cómo podemos crear efectos secundarios en reacción a los cambios de estado con los watchers.

Evitar mutar el 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 computado de retorno debe ser tratado como de sólo lectura y nunca ser mutado. - en su lugar, actualiza el estado fuente del que depende para desencadenar nuevos cómputos.

Propiedades Computadas has loaded