Vue и computed properties

В Vue вычисляемые свойства (computed properties) представляют собой механизм реактивного пересчёта значений на основе зависимостей. Их ключевая особенность заключается в кэшировании результата до тех пор, пока не изменится хотя бы одна реактивная зависимость внутри функции вычисления.

В отличие от методов, computed-свойства пересчитываются только при необходимости, что делает их предпочтительным инструментом для производных данных.


Базовый принцип работы computed

Каждое вычисляемое свойство в Vue представляет собой функцию, результат которой автоматически кэшируется системой реактивности.

export default {
  data() {
    return {
      firstName: 'Иван',
      lastName: 'Петров'
    }
  },
  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`
    }
  }
}

В данном примере fullName зависит от firstName и lastName. Пока эти значения не изменяются, повторные обращения к fullName не приводят к повторному выполнению функции.


Отличие computed от методов

Методы выполняются при каждом вызове, независимо от изменений зависимостей. Computed выполняется только при изменении реактивных данных.

methods: {
  getFullName() {
    return `${this.firstName} ${this.lastName}`
  }
}

Каждый рендер компонента вызовет getFullName() заново, даже если данные не изменились.


Реактивные зависимости и отслеживание

Vue автоматически отслеживает зависимости внутри computed-функции. Это происходит на этапе выполнения геттеров реактивных свойств.

computed: {
  totalPrice() {
    return this.items.reduce((sum, item) => sum + item.price, 0)
  }
}

Здесь totalPrice зависит от items. Если изменится содержимое массива, Vue пометит computed как “грязное” и пересчитает его при следующем обращении.


Ленивое вычисление

Computed свойства вычисляются лениво: только тогда, когда к ним обращаются.

computed: {
  expensiveCalculation() {
    return heavyComputation(this.data)
  }
}

Если expensiveCalculation не используется в шаблоне или коде, функция не будет выполняться вообще.


Кэширование результата

Кэширование является ключевым механизмом оптимизации.

При первом доступе:

  1. выполняется функция computed
  2. результат сохраняется
  3. зависимости регистрируются

При последующих обращениях:

  • возвращается сохранённое значение

Пока зависимости не изменились, пересчёт не происходит.


Setter и getter в computed

Computed свойства могут быть как только для чтения, так и с возможностью записи через setter.

computed: {
  fullName: {
    get() {
      return `${this.firstName} ${this.lastName}`
    },
    set(value) {
      const parts = value.split(' ')
      this.firstName = parts[0]
      this.lastName = parts[1] || ''
    }
  }
}

Теперь изменение fullName автоматически разложит строку на отдельные поля.


Использование computed для фильтрации данных

Частый сценарий — производные списки.

data() {
  return {
    users: [
      { name: 'Anna', active: true },
      { name: 'Oleg', active: false }
    ],
    showActive: true
  }
},
computed: {
  filteredUsers() {
    return this.users.filter(user => {
      return this.showActive ? user.active : true
    })
  }
}

При изменении users или showActive список пересчитывается автоматически.


Зависимости внутри зависимостей

Computed может опираться на другие computed свойства, формируя цепочку реактивных вычислений.

computed: {
  subtotal() {
    return this.items.reduce((s, i) => s + i.price, 0)
  },
  tax() {
    return this.subtotal * 0.2
  },
  total() {
    return this.subtotal + this.tax
  }
}

Vue корректно отслеживает всю цепочку зависимостей.


Потенциальные проблемы чрезмерного использования

Чрезмерное использование computed может привести к:

  • сложной цепочке зависимостей
  • трудностям в отладке
  • неочевидным пересчётам

Особенно проблемны случаи, когда computed выполняет побочные эффекты или сложные асинхронные операции, что нарушает концепцию чистой функции.


Асинхронные ограничения

Computed свойства не предназначены для асинхронных операций.

computed: {
  async data() {
    const res = await fetch('/api/data')
    return await res.json()
  }
}

Подобный подход некорректен, поскольку computed должен возвращать синхронное значение. Для асинхронности используются методы жизненного цикла или внешние сторы состояния.


Взаимодействие с реактивностью Vue 3

В Composition API computed реализуется через функцию computed, возвращающую ref.

import { computed, ref } from 'vue'

const price = ref(100)
const count = ref(2)

const total = computed(() => price.value * count.value)

Значение total.value автоматически обновляется при изменении зависимостей.


Writable computed в Composition API

import { computed, ref } from 'vue'

const firstName = ref('Иван')
const lastName = ref('Петров')

const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value) => {
    const parts = value.split(' ')
    firstName.value = parts[0]
    lastName.value = parts[1] || ''
  }
})

Запись в fullName.value приводит к обновлению базовых реактивных значений.


Оптимизация с computed

Computed используется как инструмент снижения вычислительной нагрузки.

Типичные оптимизации:

  • кэширование фильтрованных списков
  • предрасчёт агрегатов (sum, avg, count)
  • мемоизация сложных вычислений
  • устранение повторных вычислений в шаблоне

Различие computed и watch

Computed возвращает значение и используется в шаблоне или логике.

Watch используется для наблюдения за изменениями и выполнения побочных эффектов.

watch: {
  firstName(newVal) {
    console.log('Имя изменилось:', newVal)
  }
}

Computed не предназначен для side effects, watch — предназначен.


Внутренний механизм реактивного пересчёта

Vue строит граф зависимостей:

  • каждая computed-функция становится реактивным узлом
  • зависимости регистрируются во время выполнения
  • изменения помечают узлы как устаревшие
  • пересчёт происходит лениво при обращении

Этот механизм позволяет минимизировать количество вычислений даже в больших приложениях.


Практика проектирования computed-свойств

Рациональная структура:

  • computed должно возвращать производное значение
  • логика должна быть детерминированной
  • отсутствие побочных эффектов
  • минимальная вложенность

Пример хорошей структуры:

computed: {
  isAdult() {
    return this.age >= 18
  },
  status() {
    return this.isAdult ? 'adult' : 'minor'
  }
}

Типичные ошибки использования

Частые ошибки включают:

  • использование computed как метода с параметрами
  • выполнение API-запросов внутри computed
  • изменение состояния внутри computed
  • дублирование сложной логики без разделения на части

Computed должен оставаться чистым вычислителем состояния.


Использование в шаблонах

В шаблоне computed используется как обычное свойство:

<div>{{ fullName }}</div>
<div>{{ total }}</div>

При этом Vue не вызывает функцию напрямую при каждом рендере, а использует кэшированное значение до изменения зависимостей.