1. Проблема обновления данных ag-Grid
В реальных проектах таблицы данных редко предоставляют только функции простого запроса. Особенно в административных системах или рабочих платформах пользователи часто могут непосредственно изменять данные или изменять состояние внутри таблицы.
Например, существует требование, чтобы при нажатии кнопки в определенной ячейке результаты вычислений в других строках немедленно обновлялись, или чтобы пользовательский интерфейс определенной ячейки в реальном времени изменялся в зависимости от глобального состояния на основе Pinia/Vuex.
В проекте для реализации таких функций использовался ag-Grid. ag-Grid предоставляет структуру на основе виртуальной прокрутки и высокопроизводительный движок рендеринга, что делает его очень подходящим для обработки больших объемов данных. Однако в процессе использования совместно с Vue 3 возникли неожиданные проблемы с рендерингом.
- Проблема 1:Проблема с перерисовкой всей таблицы Grid при изменении одной ячейки
При прямом изменении rowData на основе ref или reactive во Vue, Vue обнаруживает изменения в целом массиве. В результате внутри ag-Grid может произойти повторная перерисовка всех строк. Особенно это проявляется, когда данных более тысячи, что вызывает временные задержки в отображении.
- Вопрос 2:Проблема непереноса актуального значения при ссылке на внешнее состояние внутри cellRenderer
В случаях, когда используются внутренние переменные или состояние Pinia, ag-Grid может удерживать значение на момент начальной отрисовки. Это связано с тем, что реактивная система Vue и логика повторного использования компонентов ag-Grid работают по-разному.
- Проблема 3:Избыточный вызов API повторяется при каждом рендеринге
Если использовать структуру, которая вызывает внешние функции или геттеры каждый раз, когда рендерится определенная ячейка, то одинаковые запросы к API и операции выполнялись бы повторно даже при изменении прокрутки или состояния. В итоге возникли проблемы с производительностью и структурой, которые нельзя было решить только с помощью простой реализации функционала.
2. Причины возникновения проблем с данными
Vue 3 использует систему реактивности на основе Proxy. Хотя это очень эффективная структура в обычных приложениях Vue, могут возникнуть проблемы при использовании с библиотеками, такими как ag-Grid, которые имеют собственный рендеринг движок. ag-Grid управляет следующими состояниями самостоятельно.
-
Строка узла / Состояние ячейки / Состояние прокрутки / Состояние выбора / Состояние транзакции
То есть, как Vue, так и ag-Grid имеют структуру, которая стремится отслеживать состояние и управлять рендерингом. Особенно если rowData отслеживается реактивно на глубоком уровне, Vue будет управлять тысячами объектов данных, обернутых в Proxy. В процессе возникают следующие проблемы.
-
Увеличение использования памяти
-
Ненужная отрисовка
-
Накладные расходы на создание прокси
-
Увеличение затрат на отслеживание состояния
Кроме того, поскольку ag-Grid использует структуру на основе виртуального прокручивания, оптимизация рендеринга очень важна, и иногда чрезмерное отслеживание реактивности Vue мешало этому. В конечном итоге основная проблема заключалась в том, какие состояния управляются Vue и какие делегируются ag-Grid.
3. Решение
① оптимизация на основе shallowRef
Первый способ решения заключался в объявлении rowData как shallowRef. Ранее для объявления rowData использовались ref или reactive, но в этом случае Vue будет отслеживать все внутренние объекты. В то время как shallowRef не отслеживает изменения свойств внутренних объектов. То есть Vue мог управлять только ссылкой на массив rowData, а фактическое состояние внутри было передано на управление ag-Grid.
Таким образом, мы смогли добиться следующего эффекта.
-
Удаление ненужного отслеживания адаптивности
-
Снижение полной перерисовки сетки
-
Снижение использования памяти
-
Улучшение производительности рендеринга
В частности, в среде Grid с тысячами данных эффект улучшения производительности был очень заметным. Кроме того, структура виртуальной прокрутки также работала значительно стабильнее. Из этого опыта я понял, что не всегда правильно полностью полагаться на реактивную систему Vue для управления состоянием. Иногда делегирование ответственности за управление состоянием библиотеке может быть гораздо более эффективной структурой.
② Пользовательский рендерер ячеек и API транзакций
Вторым решением было использование Пользовательского рендерера ячеек и API транзакций. ag-Grid имеет структуру для повторного использования компонентов ячеек. Это значит, что при прокрутке не создаются заново все компоненты, а переиспользуются существующие компоненты.
Проблема в том, что в этом процессе ссылка на внешнее состояние иногда не обновляется до последнего значения. Для решения этой проблемы в проекте был явно реализован метод refresh.
Это было настроено так, чтобы при изменении определенных данных обновлялась только соответствующая ячейка. Также был изменен способ обновления данных. Ранее данные изменялись следующим образом:
-
rowData.value[0].status = ‘Готово’
Но этот подход оказался неэффективной структурой как для Vue, так и для ag-Grid. В конечном итоге структура была изменена на прямое использование API applyTransaction ag-Grid в проекте.
Transaction API может явно передавать следующие действия.
-
add / update / remove
Важнейшим преимуществом этой структуры было то, что она позволяет выполнять «частичное обновление». То есть можно обновлять только фактически изменённые строки, не перерисовывая всю сетку. В результате этого можно достичь таких эффектов, как следующие.
-
Сокращение области рендеринга
-
Увеличение производительности
-
Увеличение стабильности прокрутки
-
Снижение вызовов API
-
Возможность контроля частичного состояния
Особенно в условиях больших данных разница стала очень заметной.
4. Оптимизация производительности и проектирование
Главный урок, который я извлек из этого процесса устранения неполадок, заключается в том, что оптимизация производительности фронтенда — это не просто исправление кода, а «проблема проектирования». Реактивная система Vue очень мощная и удобная.
Однако, когда она комбинируется с библиотеками, такими как ag-Grid, которые имеют собственный движок рендеринга, ответственность за управление состоянием дублируется, что может привести к проблемам с производительностью. В конце концов, важно было задать следующий вопрос.
-
Какое состояние будет управлять Vue?
-
Какое состояние будет делегировано ag-Grid?
-
Насколько далеко будет отслеживаться реактивность?
-
Какие изменения будут обрабатываться как транзакция?
То есть, разделение ролей между фреймворком и библиотекой было ключевым. Также в этом опыте я понял, что понимание внутренних механизмов гораздо важнее, чем простая реализация функций.
5. Заключение
Этот опыт оптимизации производительности ag-Grid был более значимым, чем простое исправление ошибок. Сначала это казалось простой проблемой рендеринга, но на самом деле это была структурная конфликтная проблема между реактивной системой Vue и движком рендеринга ag-Grid. Особенно я смог глубоко прочувствовать следующие элементы.
-
Структура рендеринга больших данных
-
Преимущества и недостатки реактивных систем
-
Управление состоянием на основе транзакций
-
Разделение ролей библиотек и фреймворков
-
Оптимизация виртуального скролла
Прежде всего, я сильно почувствовал важность проектирования, заключающегося в том, что следует доверить фреймворку и что стоит делегировать библиотеке. В заключение, этот опыт был очень значимым, так как он позволил мне глубже понять проектирование производительности и структуры управления состоянием в масштабной фронтенд-среде, выходящей за рамки простого опыта использования Vue или ag-Grid.
<Список литературы>
- официальная документация ag-Grid (https://www.ag-grid.com/vue-data-grid/getting-started/ )
- Официальная документация Vue (https://ko.vuejs.org/api/reactivity-advanced)
люси