Кэширование computed свойств

В Vue.js computed свойства представляют собой реактивные вычисляемые значения, которые зависят от состояния компонента. Основная особенность computedкэширование результатов. Это означает, что вычисление свойства выполняется только тогда, когда изменяются его зависимости. При повторном доступе к свойству без изменений зависимостей возвращается ранее вычисленное значение. Кэширование повышает производительность, особенно при сложных вычислениях.

Принципы работы кэширования

  1. Ленивая и мемоизированная оценка computed свойства вычисляются по запросу. Они не выполняются при каждом рендере компонента, а только тогда, когда доступ к свойству фактически необходим. Если зависимые реактивные свойства (data или другие computed) не изменились, Vue возвращает сохранённое значение, избегая повторных вычислений.

  2. Отслеживание зависимостей Vue автоматически отслеживает, какие реактивные свойства используются внутри computed. Если какая-либо зависимость изменяется, Vue помечает computed как “грязное” (dirty), и при следующем обращении вычисление выполняется заново.

  3. Кэширование по компоненту Каждое computed свойство кэшируется в рамках конкретного экземпляра компонента. Разные экземпляры компонента имеют свои отдельные кэши, что гарантирует независимость состояния.

Пример computed свойства

import { defineComponent, ref, computed } from 'vue';

export default defineComponent({
  setup() {
    const numbers = ref([1, 2, 3, 4]);

    const sum = computed(() => {
      console.log('Вычисляем сумму');
      return numbers.value.reduce((acc, n) => acc + n, 0);
    });

    return { numbers, sum };
  }
});

В этом примере sum будет вычислено только при первом доступе. Если numbers не меняется, последующие обращения к sum не вызовут повторного вызова функции reduce. Консоль выведет сообщение только при фактическом пересчёте.

Тестирование кэширования с Vue Test Utils

Vue Test Utils предоставляет инструменты для монтирования компонентов и проверки их реактивного поведения. Для проверки кэширования computed свойств важно убедиться, что функция вычисления вызывается корректное количество раз при изменении зависимостей.

Монтирование компонента
import { mount } from '@vue/test-utils';
import SumComponent from '@/components/SumComponent.vue';

describe('SumComponent', () => {
  it('должно кэшировать computed свойство', async () => {
    const wrapper = mount(SumComponent);
    
    // Первый доступ к computed, функция вычисляется
    expect(wrapper.vm.sum).toBe(10);

    // Повторный доступ без изменения зависимостей, функция не вызывается
    expect(wrapper.vm.sum).toBe(10);
  });
});
Проверка повторного вычисления при изменении зависимостей
it('пересчитывает computed при изменении зависимостей', async () => {
  const wrapper = mount(SumComponent);

  wrapper.vm.numbers.push(5);
  
  // Используем nextTick для ожидания реактивного обновления
  await wrapper.vm.$nextTick();

  expect(wrapper.vm.sum).toBe(15);
});

Здесь важно понимать, что computed кэширует результаты только до изменения зависимостей. После изменения numbers свойство sum помечается как “грязное” и вычисляется заново.

Мокирование и шпионаж функций

Для глубокого тестирования кэширования полезно отслеживать фактические вызовы функций, используемых внутри computed. В Jest можно использовать jest.fn():

import { mount } from '@vue/test-utils';
import { ref, computed, defineComponent } from 'vue';

const spyFn = jest.fn(() => 42);

const TestComponent = defineComponent({
  setup() {
    const value = ref(1);
    const computedValue = computed(() => spyFn(value.value));
    return { value, computedValue };
  }
});

describe('TestComponent', () => {
  it('вызывает функцию computed только при необходимости', () => {
    const wrapper = mount(TestComponent);

    // Первый доступ вызывает функцию
    wrapper.vm.computedValue;
    expect(spyFn).toHaveBeenCalledTimes(1);

    // Повторный доступ без изменений зависимостей не вызывает функцию
    wrapper.vm.computedValue;
    expect(spyFn).toHaveBeenCalledTimes(1);

    // Изменение зависимости вызывает повторный вызов
    wrapper.vm.value = 2;
    wrapper.vm.computedValue;
    expect(spyFn).toHaveBeenCalledTimes(2);
  });
});

Такой подход позволяет точно контролировать поведение кэширования и убедиться, что Vue не выполняет лишние вычисления.

Особенности кэширования в Composition API и Options API

  • Composition API: computed определяется через функцию computed, кэширование работает на уровне возвращаемого реактивного свойства.
  • Options API: computed определяется в объекте computed, кэширование аналогично, но Vue отслеживает зависимости автоматически на уровне экземпляра.

Советы по тестированию

  • Использовать nextTick после изменения зависимых реактивных свойств, чтобы убедиться, что Vue завершил реактивное обновление.
  • Применять шпионы для функций внутри computed, чтобы проверять фактическое число вызовов.
  • При сложных вычислениях проверять как значения, так и побочные эффекты, если они есть.
  • Для массивов и объектов важно помнить, что Vue отслеживает реактивные изменения, а не просто присваивание нового значения.

Влияние на производительность

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