Концепция временного View state в JavaScript
Приветствую всех! В данной статье, речь пойдёт о достаточно необычной теме, информацию о которой я почему-то не нашёл, хотя она достаточно полезна в современных JavaScript фреймворках и библиотеках для создания пользовательских интерфейсов, ведь, в некоторых случаях, применение концепции может помочь ускорить работу с DOM в несколько раз.
Название условно, но важна именно суть.
Проблема обычного state
Под понятием «обычного state» подразумеваются данные, которые сохраняются непосредственно благодаря state менеджеров, либо благодаря внутреннего функционала фреймворка или библиотеки. Пример состояния во Vue.js:
createApp({
setup() {
return {
count:ref(0);
};
},
template: `
Clicks: {{ count }}
`,
}).mount("#app");
В данном случае, состояние хранится непосредственно в объекте, который возвращается в предопределённом методе фреймворка.
Так вот, узлы DOM могут зависеть от данного состояния путём разных синтаксических конструкций. В примере, такой конструкцией является строчка {{ clicks }}
, которая меняется на текущие данные благодаря интерполяции строк.
Также, часто используемой синтаксической конструкцией является «цикл». Цикл представляет собой ключевое слово, либо атрибут, либо метод, который явно определяет, что будет происходить создание DOM узлов, зависящих от количества элементов и от самих значений, идущих от состояния. В данной статье я рассматриваю данную тему более конкретно. Пример цикла:
{{ id }}
{{ label }}
Допустим, мы хотим обновить class
элемента. Данные у нас приходят в виде массива объектов. Понятное дело, что в объекте нужно явно указать ключ: значение класса и в двойных фигурных скобках по ключу всё получить, но в этом и заключается основная проблема, потому что-то это медленно.
В подтверждении своих слов, я возьму бенчмарки фреймворка cample.js (в процессе разработке которого я как раз и заметил подобную проблему). Там явно видно, что класс, зависящий напрямую от данных обычного состояния, ставится медленнее, чем класс, который использует временный View state.
Возьмём две версии cample.js: 3.2.0-alpha.45 и 3.2.1-beta.0. Там есть такая строчка как «select row» (4 строчка), в ней как раз и заключается основное отличие:
1. Сравнение 3.2.0-alpha.45 и 3.2.1-beta.0 по скорости.
Данные взяты из 126 и 128 релизов бенчмарка.
Как видно из изображения, разница между одним результатом и другим почти в полтора раза. Я долго думал над тем, почему так? Я раньше предполагал, что код просто медленный, но факт в другом. Если данные идут через обычный state, то возникает необходимость пройтись по всем данным, даже если у нас всего меняется одна буква в значении свойства в n — порядковом объекте.
const oldData = [
{
id: 1,
label: "Текст 1",
},
{ id: 2, label: "Текст 2" },
{
id: 3,
label: "Текст 3",
}
];
const newData = [
{
id: 1,
label: "Текст 11", // 1 итерация поменялась одна буква, но всё равно смотрим дальше
},
{ id: 2, label: "Текст 2" }, // 2 итерация
{
id: 3,
label: "Текст 3", // 3 итерация
}
];
Поэтому, это будет всегда медленно, но это логически правильный подход и в этом как бы заключается основной прикол всех современных фреймворков и библиотек для создания пользовательских интерфейсов. Но, какая же может быть альтернатива этому подходу?
Временный View state
Специально для такой проблемы, когда необходимо ввести отдельное состояние от основного, чтобы не проходить по элементам несколько раз, можно использовать некую концепцию в коде, которая позволит сделать привязку не к объекту, а к элементу. Этой концепцией является временный View state.
Суть его такова: Мы создаём отдельный массив, для каждого элемента. Он будет находится в коде самого модуля, а пользователю мы выдаём методы, которые с этим массивом взаимодействуют, в callback функцию. Таким образом, в модуле будет храниться примерно такой код:
{
el:li,
temporaryViewState:[{class:"value"}]
}
А на клиенте примерно такой:
setClass: [
(setData, event, eachTemporaryViewState) => () => {
const { setTemporaryViewState, clearTemporaryViewState } = eachTemporaryViewState;
clearTemporaryViewState();
setTemporaryViewState(() => {
return { class: "value" };
});
},
"updateClass",
],
Также, данный массив можно создавать только тогда, когда вызывается callback функция, либо же создать просто один массив для всех элементов. Это позволит не привязываться к данным, которые идут от обычного состояния, а привязываться уже к конкретному элементу, который мы хотим обновить. То-есть, мы как бы создаём временное состояние, которое можно очистить и перезаписать. Это хорошо подойдёт для тех случаев, когда мы хотим работать с неконтролируемыми элементами:
То-есть, он просто не зависит от обычного состояния напрямую, поэтому в DOM данный узел, можно сказать, будет статичным (если мы делаем шаблон узла, то данный элемент будет скипаться).
Таким образом, у нас есть состояние, которое зависит только лишь от конкретного элемента и от callback функции. При работе с «циклом» не придётся обходить весь массив данных, чтобы обновить одну букву в одном элементе. Достаточно будет просто вызвать конкретную функцию у конкретного элемента и обновить конкретный класс.
Это и позволит добиться быстрого результата в работе с данными и DOM. Такую концепцию вполне возможно применить в современных фреймворках и библиотеках и работать с ней.