Мемоизация данных

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

В интерфейсных приложениях мемоизация применяется для:

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

В экосистеме Vue 3, на которой построена библиотека Element Plus, мемоизация реализуется через реактивные механизмы и специализированные API.


Роль мемоизации в архитектуре Element Plus

Библиотека Element Plus активно использует реактивную систему Vue для оптимизации работы компонентов. Компоненты интерфейса часто работают с:

  • большими таблицами данных;
  • динамическими списками;
  • фильтрацией;
  • сортировкой;
  • вычисляемыми стилями и состояниями.

Без мемоизации такие операции могут приводить к множественным повторным вычислениям при каждом обновлении состояния.

Мемоизация позволяет:

  • минимизировать повторные вычисления;
  • сохранять вычисленные результаты;
  • автоматически пересчитывать значения только при изменении зависимостей.

Вычисляемые свойства как форма мемоизации

Основным механизмом мемоизации в Vue и Element Plus являются вычисляемые свойства (computed).

Вычисляемое свойство:

  • выполняется один раз;
  • кэширует результат;
  • повторно вычисляется только при изменении зависимостей.

Пример

import { computed, ref } from 'vue'

const items = ref([
  { id: 1, price: 100 },
  { id: 2, price: 200 },
  { id: 3, price: 300 }
])

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

Особенности:

  • totalPrice вычисляется только при изменении items;
  • повторные обращения возвращают кэшированный результат;
  • не происходит повторного выполнения reduce.

Это базовая форма мемоизации, встроенная в реактивную систему Vue.


Мемоизация при работе с таблицами Element Plus

Компонент таблицы часто работает с большим количеством данных.

Element Plus Vue.js

Без оптимизации операции:

  • фильтрации;
  • сортировки;
  • группировки;

могут выполняться при каждом обновлении компонента.

Пример мемоизации фильтрации

const search = ref('')
const rows = ref([...])

const filteredRows = computed(() => {
  return rows.value.filter(row =>
    row.name.toLowerCase().includes(search.value.toLowerCase())
  )
})

Компонент таблицы:

<el-table :data="filteredRows">
  <el-table-column prop="name" label="Name" />
</el-table>

Преимущества:

  • фильтрация выполняется только при изменении search или rows;
  • повторные рендеры таблицы не вызывают повторную фильтрацию.

Мемоизация сложных вычислений

В интерфейсных приложениях могут выполняться дорогостоящие операции:

  • статистические расчёты;
  • агрегация данных;
  • построение структурированных списков;
  • подготовка данных для визуализации.

Пример агрегации

const statistics = computed(() => {
  const result = {}

  for (const item of items.value) {
    if (!result[item.category]) {
      result[item.category] = 0
    }

    result[item.category] += item.price
  }

  return result
})

Такой подход предотвращает:

  • повторный перебор массива;
  • лишние вычисления при каждом рендере.

Мемоизация и watchEffect

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

Для таких случаев применяется watchEffect.

import { watchEffect } from 'vue'

watchEffect(() => {
  console.log(filteredRows.value)
})

Однако watchEffect не выполняет мемоизацию. Он лишь отслеживает зависимости.

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


Ленивая мемоизация

Вычисляемые свойства обладают ленивой стратегией вычисления.

Это означает:

  • вычисление происходит только при обращении;
  • если значение не используется — вычисление не выполняется.

Пример

const heavyCalculation = computed(() => {
  console.log('expensive calculation')
  return items.value.map(x => x.price * 1.2)
})

Если heavyCalculation не используется в шаблоне или коде:

  • функция не будет выполнена.

Это особенно важно при сложных интерфейсах.


Мемоизация и реактивные зависимости

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

Если внутри computed используются:

  • ref
  • reactive
  • другие computed

Vue автоматически отслеживает зависимости.

Пример

const tax = ref(0.2)

const total = computed(() => {
  return totalPrice.value * (1 + tax.value)
})

При изменении tax:

  • кэш total сбрасывается;
  • значение пересчитывается.

Кэширование функций

Иногда требуется мемоизация функций, а не только вычисляемых значений.

Для этого используется собственная реализация мемоизации.

Пример

function memoize(fn) {
  const cache = new Map()

  return function (...args) {
    const key = JSON.stringify(args)

    if (cache.has(key)) {
      return cache.get(key)
    }

    const result = fn(...args)
    cache.set(key, result)

    return result
  }
}

Использование:

const expensiveFunction = memoize((value) => {
  return value * value
})

При повторных вызовах:

expensiveFunction(5)
expensiveFunction(5)

второй вызов возвращает результат из кэша.


Мемоизация в пользовательских composable

В архитектуре Vue 3 часто используются composable-функции.

Мемоизация позволяет избежать повторной подготовки данных.

Пример composable

import { computed } from 'vue'

export function useSortedData(data) {
  const sorted = computed(() => {
    return [...data.value].sort((a, b) => a.price - b.price)
  })

  return {
    sorted
  }
}

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

const { sorted } = useSortedData(products)

сортировка будет выполняться только при изменении products.


Мемоизация и виртуализация списков

При работе с большими таблицами и списками Element Plus часто применяется:

  • виртуализация;
  • ленивые вычисления;
  • мемоизация данных.

Это особенно актуально для компонентов:

  • таблиц;
  • деревьев;
  • селектов с большим количеством элементов.

Пример:

const visibleItems = computed(() => {
  return items.value.slice(start.value, end.value)
})

Результат:

  • пересчитывается только при изменении диапазона;
  • уменьшает объём данных, передаваемых компоненту.

Потенциальные проблемы мемоизации

Избыточная мемоизация

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

  • усложнению логики;
  • большому количеству зависимостей;
  • усложнённой отладке.

Мемоизация должна применяться только для:

  • дорогостоящих вычислений;
  • часто используемых данных.

Мутация объектов

Если вычисляемое свойство возвращает объект, который затем изменяется, возможны ошибки.

Пример проблемы:

const settings = computed(() => {
  return {
    dark: theme.value === 'dark'
  }
})

Если объект будет изменён:

settings.value.dark = false

кэш может стать неконсистентным.

Безопасный вариант:

  • не мутировать результат;
  • возвращать новые объекты.

Зависимости вне реактивной системы

Если внутри computed используется не реактивная переменная, мемоизация может работать некорректно.

Пример:

let multiplier = 2

const value = computed(() => {
  return price.value * multiplier
})

Изменение multiplier:

multiplier = 3

не вызовет пересчёт value.

Решение — использовать ref.

const multiplier = ref(2)

Мемоизация и производительность интерфейса

Правильное применение мемоизации позволяет значительно повысить производительность приложений, использующих Element Plus.

Основные преимущества:

  • уменьшение количества вычислений;
  • сокращение числа перерисовок;
  • более стабильная работа интерфейса;
  • снижение нагрузки на JavaScript-движок.

Особенно заметен эффект при работе с:

  • таблицами с тысячами строк;
  • сложными формами;
  • динамическими фильтрами;
  • статистическими вычислениями.

Мемоизация становится важным инструментом оптимизации при разработке масштабных интерфейсов на базе Vue и Element Plus.