Вы предлагаете создать состояние, и периодически синхронизовывать его с DOM. Это тот же React way. HTMLElement это уже состояние.
Нет, я предлагаю сделать обычные геттеры-сеттеры. Что-то вроде этого:
get view() {
return this.getAttribute("view");
}
set view(value) {
this.setAttribute("view", value);
}
Не имеет смысла хранить что-то в дополнительном состоянии объекта элемента. Впрочем, ладно, может быть и имеет смысл именно только в точечных изменениях.
Не нужно недооценивать производительность DOM API, это не React
У меня такое ощущение, что что угодно будет быстрее чем реакт )
Честно говоря, здесь вопрос больше не про производительность, а про удобство отладки. Именно отладка - самая большая боль.
К примеру, у нас есть какой-либо код, который делает что-то вроде этого:
// в родителе
elem.setAttribute("attr1", "val1");
elem.setAttribute("attr2", "val2");
elem.setAttribute("attr3", "val3");
// ну или так
elem.attr1 = val1;
...
elem.attr3 = val3;
В этом случае у нас будет 3 разных рендера (ну, можно назвать обновлениями DOM, чтобы не было путаницы). Особенно интересно, если в каждом колбеке на setAttribute будут меняться еще и другие свойства через тот же setAttribute... Отследить изменения становится довольно проблематичным.
И, допустим, мы столкнулись с багом и хотим понять почему у нас что-то не то отображается. В случае батча нам достаточно поставить debugger перед этим кодом и debugger в функции рендера, где мы уже точно сможем отследить текущее состояние атрибутов (синхронизируемых свойств) и понять откуда идет ошибка. Какое сочетание вызывает проблему.
Ну а насчет убийства разметки, иногда проще полностью пересоздать заново группу элементов, чем модифицировать их значения. В том же дейт-пикере нет особой необходимости хранить таблицы годов и месяцев в DOM и скрывать их через стили. Да и многие гайды рекомендуют все-таки пересоздавать элементы, чтобы не засорять DOM.
Почему я это все спрашиваю? Я еще сам не определился в оптимальном подходе к веб-компонентам. Никто по сути еще не знает, как их правильно готовить. Поэтому чтобы эта отличная, на мой взгляд, технология получила распространение, нужно найти какой-либо общий подход. Чтобы вообще можно было отключить голову и следовать определенному шаблону. Пока что мои эксперименты показывают, что проще все-таки обрабатывать изменения в одной-единственной функции. На этом, собственно, и были построены классовые компоненты реакта. Кроме того, подход, что мы просто меняем свойства (по сути состояние), и, когда придет время (когда окажется, что больше ничего не вызывает синхронные изменений свойств), тогда уже рендерим (обновляем) - это довольно знакомый шаблон.
Тоже с веб-компонентом. То, что вы подразумеваете под рендером это вставка в DOM, и это происходит один раз – element.shaddowRot.innerHtml = someHtml. Это все.
Не совсем. Я не про connectedCallback. Я подразумеваю, такое. Допустим, мы делаем какой-нибудь date-picker. При переключениях месяцев у нас перестраивается таблица с числами. Или при нажатии на выбор месяца у нас открывается табличка с месяцем и годом. И вот, к примеру, за это отвечает атрибут view. При этом может меняться атрибут с отображаемым месяцем и годом. При одновременной установке нескольких атрибутов (если не батчить их) там будет выполняться довольно много операций.
Превосходная статья. Добавил в закладки. Несколько вопросов. 1) судя по коду, вы не используете attributeChangeCallback. Не рассматривали ли Вы такой алгоритм. Создаем общую функцию рендера, в которой мы сравниваем значения атрибутов, и если изменились, только тогда меняем часть DOM. Ну и плюс у нас однозначно будет какая-то общая логика. Т.е. что-то такое: ```
render() {
if (this.hasChanged("attr1")) {
this.doSmth1(); // какая-то тяжелая операция
}
if (this.hasChanged("attr2")) {
this.doSmth2();
}
// общая логика для любого рендера
}
А в attributeChangeCallback мы добавляем батчинг операций с помощью очереди микрозадач. Что-то такое: ```
attributeChangedCallback(
name: string,
oldValue: string | boolean,
newValue: string | boolean,
) {
if (oldValue === newValue) return;
this.lastChangedAttr = name;
queueMicrotask(() => {
if (this.lastChangedAttr !== name) return;
// prerenderAttrs - Map, в которой хранятся значения атрибутов
// (синхронизированных свойств) ДО рендера
// сравнение изменений в функции рендера идет с соответстующим значение в нем
const noChanges = [...this.prerenderAttrs.entries()]
.every(([attr, value]) => this[attr] === value);
if (noChanges) return;
this.render();
this.lastChangedAttr = "";
this.prerenderAttrs
.forEach((value, attr) => this.prerenderAttrs.set(attr, this[attr]));
});
}
2) насколько легко использовать adoptedStyleSheets при разработке? Насколько я помню, ссылку на соответствующий файл в девтулзах не найти (даже в FF). Не рассматривали ли использование какого-либо статического свойства 'defaultStyles`, в котором вы можете хранить, к примеру, тег style, ссылку на таблицу и т.п. Которое можно переписать на любом проекте (до инициализации компонента).
А вы пробовали действительно применять все то, о чем вы пишите на практике? Я про скриншоты и тестирование через play-функции? Скриншоты настроить без использования их хроматика - не очень простая задача. Play-функции - кривая обертка над playwright. Тесты гораздо более хрупкие. А постоянные релизы, ломающие предыдущую функциональность... Они только библиотеку тестов переписывали несколько раз. Как я понимаю, все ради монетизации хроматика... Я бы вот вообще не рекомендовал использовать эту кривую поделку в нормальных проектах. Там только модулей за 10+ тысяч...
симптомы глубокой системной болезни, которую можно назвать «постсоветский управленческий синдром»
Так уж прям "постсоветский" ?
Это приводит к внедрению нерабочих схем, потере ценных идей, рожденных «на передовой», и созданию в коллективе атмосферы страха, где люди предпочитают молчать, даже видя очевидные ошибки.
Ага. А рационализаторов в клятом совке расстреливала кровавая гебня. Или даже лично Сталин после обеда.
Фундаментом же многих проблем остается отношение к персоналу как к статье расходов, а не к ключевому активу.
Я так понимаю, фразу Сталина "кадры решают все" Вы не слышали..
Будущее строится не на выжимании соков, а на выращивании талантов.
Это в совке могли уволить одним днем, а бесплатные кружкИ для детей - подарок атлантов капиталистического рая?
О, кажется у нас возник диспут... Это я люблю ) Ну еще мы, кажется, не совсем поняли друг друга, когда говорили об утечках памяти. Я имел в виду утечки при удалении слушателя, когда стор остается. Вот и дал демку с удалением output, когда через некоторое время надпись в консоли исчезает, поскольку сборщик мусора отработал.
А это значит что никаких утечек при удалении стора/компонента.
К сожалению, относительно функции прослушки это не так. Вот я создал демку. При удалении слушателя не удаляется функция. Только вручную, поскольку ссылка на слушатель (переменнаяoutput, сохраненная в замыкании) так и хранится в памяти. В консоли постоянная ошибка Cannot set properties of null (setting 'textContent'). Что полностью совпадает с поведением самой первой реализации. Если удалить стор, слушатели удалятся в обоих случаях.
За использование EventTarget отдельный респект и уважуха )
Не пойму, а чем это принципиально отличается от самой первой реализации? Эмиттер неявно будет хранить все слушатели. Остальные проблемы там и остались вроде... Ну и вместо store.value = "abc" нужно будет писать store.set({value: "abc"}).
Вы имеете в виду CustomEvent? Если да, то их нужно диспатчить на уровне window (или document), поскольку потребители могут находиться вообще в разных местах DOM-дерева, и эти события не поймаешь на всплытии/погружении. Поэтому при удалении потребителя, нужно будет принудительно очищать слушатель, чтобы не засорять память. Поэтому где-то хранить слушатели все-таки придется.
1) а есть реализованный пример нескольких страниц, чтобы можно было увидеть, как там подгружаются скрипты и стили при переходе по ссылкам? Что-то у меня подозрение, что если сделать все по-уму, там будет далеко не один экран.. 2) ну.. я бы не сказал, что это прям вот декларативно ) Кроме того, с таким подходом очень резко ограничивается область применения Fusor. Опять же непонятно, чем же это лучше тех же веб-компонентов, если фактически update вызывается императивно.
Особо нет времени смотреть на код. Просмотрел только бегло. Сразу возникли вопросы. 1) я вижу, что в примере роуты у Вас через #. Значит ли это, что вы сразу полностью грузите все приложение со всеми страницами? Или это только для примера в codepen? 2) разве в вашем фреймворке (то, от чего я призываю отказаться) можно вставлять теги как есть? Или только через вашу абстракцию? 3) если все-таки нормальные теги (элементы) вставлять можно, то как вы намерены обрабатывать случаи, когда эти элементы исчезают из DOM? Допустим, элемент уничтожен каким-либо способом. Как в таком случае отследить этот момент и очистить обработчик, чтобы ссылка на этот элемент в обработчике не препятствовала работе сборщика мусора?
Ну и динамические роуты сейчас нужны плюс-минус постоянно. Даже для сайтов. Поэтому важный момент.
Логично. Добавил пример. https://grigorenkosergey.github.io/native-SPA/pages/dynamic/12 Правда, реализовал на коленках, может что-то еще упустил. Может потом внесу UPD в статью. По приоритетам.. Если высчитывать совпадение с от наиболее специфичного к наименее специфичному, то особых проблем быть не должно.
А Вы эти мигания на практике наблюдали? Потому что вообще говоря, если делать правильно, их быть не должно.
На MDN постоянно вижу, когда перехожу по страницам. Несмотря на то, что там стили и js полностью берутся из кеша.
Хм. Не могу придумать подходящий кейс.
Ну.. Мне тоже сходу в голову не приходит ) Но я уже говорил об этом, что "React со своим роутером нанес непоправимый урон моей психике". Потом проведу чуть более полное исследование этого вопроса.
P.S. на всякий случай: рассматривали для Ваших задач какой-нибудь Astro, или solid-start?
Ну да, как вариант. Только потом TS прикрутить сложно будет без сборщика. Ну и я именно исходные htm/css файлы использовал для того, чтобы автодополнение, которое дает emmet из коробки работало безупречно. Например, такие штуки .parent>.child*5.
Кстати, интересно что конкретно не получилось сделать с vite.
Честно говоря, уже не помню сути... Помню, что там внутренний сервер, который поднимается при разработке, работает совсем не так, как должен работать реальный сервер после деплоя (или как я изначально ожидал). Вроде еще были проблемы при предобработке html - файлов... Ну а может дело во врожденной кривизне рук ) Насчет необходимости роутера.. Здесь вопрос, конечно, спорный. Я его сделал для возможности разработки именно приложений, в которых есть какое-либо сохраняемое состояние. Динамические роуты - здесь вопрос добавления нескольких регулярок и небольшой доработки. Я не стал с этим заморачиваться по причине отсутствия необходимости. Ну и js выполняется не мгновенно. До того момента как JS отработает и отрисуются наши веб-компоненты пройдет некоторое время. Поэтому могут быть мигания. Когда же в окне компоненты уже зарегистрированы - такой проблемы нет.
Нет, я предлагаю сделать обычные геттеры-сеттеры. Что-то вроде этого:
Не имеет смысла хранить что-то в дополнительном состоянии объекта элемента. Впрочем, ладно, может быть и имеет смысл именно только в точечных изменениях.
У меня такое ощущение, что что угодно будет быстрее чем реакт )
Честно говоря, здесь вопрос больше не про производительность, а про удобство отладки. Именно отладка - самая большая боль.
К примеру, у нас есть какой-либо код, который делает что-то вроде этого:
В этом случае у нас будет 3 разных рендера (ну, можно назвать обновлениями DOM, чтобы не было путаницы). Особенно интересно, если в каждом колбеке на
setAttributeбудут меняться еще и другие свойства через тот жеsetAttribute... Отследить изменения становится довольно проблематичным.И, допустим, мы столкнулись с багом и хотим понять почему у нас что-то не то отображается. В случае батча нам достаточно поставить
debuggerперед этим кодом иdebuggerв функции рендера, где мы уже точно сможем отследить текущее состояние атрибутов (синхронизируемых свойств) и понять откуда идет ошибка. Какое сочетание вызывает проблему.Ну а насчет убийства разметки, иногда проще полностью пересоздать заново группу элементов, чем модифицировать их значения. В том же дейт-пикере нет особой необходимости хранить таблицы годов и месяцев в DOM и скрывать их через стили.
Да и многие гайды рекомендуют все-таки пересоздавать элементы, чтобы не засорять DOM.
Почему я это все спрашиваю? Я еще сам не определился в оптимальном подходе к веб-компонентам. Никто по сути еще не знает, как их правильно готовить. Поэтому чтобы эта отличная, на мой взгляд, технология получила распространение, нужно найти какой-либо общий подход. Чтобы вообще можно было отключить голову и следовать определенному шаблону. Пока что мои эксперименты показывают, что проще все-таки обрабатывать изменения в одной-единственной функции. На этом, собственно, и были построены классовые компоненты реакта. Кроме того, подход, что мы просто меняем свойства (по сути состояние), и, когда придет время (когда окажется, что больше ничего не вызывает синхронные изменений свойств), тогда уже рендерим (обновляем) - это довольно
знакомый шаблон.Не совсем. Я не про connectedCallback. Я подразумеваю, такое. Допустим, мы делаем какой-нибудь date-picker. При переключениях месяцев у нас перестраивается таблица с числами. Или при нажатии на выбор месяца у нас открывается табличка с месяцем и годом. И вот, к примеру, за это отвечает атрибут view. При этом может меняться атрибут с отображаемым месяцем и годом. При одновременной установке нескольких атрибутов (если не батчить их) там будет выполняться довольно много операций.
Превосходная статья. Добавил в закладки. Несколько вопросов.
1) судя по коду, вы не используете attributeChangeCallback. Не рассматривали ли Вы такой алгоритм.
Создаем общую функцию рендера, в которой мы сравниваем значения атрибутов, и если изменились, только тогда меняем часть DOM. Ну и плюс у нас однозначно будет какая-то общая логика. Т.е. что-то такое:
```
А в attributeChangeCallback мы добавляем батчинг операций с помощью очереди микрозадач. Что-то такое:
```
2) насколько легко использовать adoptedStyleSheets при разработке? Насколько я помню, ссылку на соответствующий файл в девтулзах не найти (даже в FF). Не рассматривали ли использование какого-либо статического свойства 'defaultStyles`, в котором вы можете хранить, к примеру, тег style, ссылку на таблицу и т.п. Которое можно переписать на любом проекте (до инициализации компонента).
А вы пробовали действительно применять все то, о чем вы пишите на практике?
Я про скриншоты и тестирование через play-функции? Скриншоты настроить без использования их хроматика - не очень простая задача. Play-функции - кривая обертка над playwright. Тесты гораздо более хрупкие.
А постоянные релизы, ломающие предыдущую функциональность... Они только библиотеку тестов переписывали несколько раз. Как я понимаю, все ради монетизации хроматика...
Я бы вот вообще не рекомендовал использовать эту кривую поделку в нормальных проектах. Там только модулей за 10+ тысяч...
Опять подмена понятий.
Так уж прям "постсоветский" ?
Ага. А рационализаторов в клятом совке расстреливала кровавая гебня. Или даже лично Сталин после обеда.
Я так понимаю, фразу Сталина "кадры решают все" Вы не слышали..
Это в совке могли уволить одним днем, а бесплатные кружкИ для детей - подарок атлантов капиталистического рая?
Может вот эта статья подойдет https://habr.com/ru/articles/962778/
О, кажется у нас возник диспут... Это я люблю )
Ну еще мы, кажется, не совсем поняли друг друга, когда говорили об утечках памяти. Я имел в виду утечки при удалении слушателя, когда стор остается.
Вот и дал демку с удалением output, когда через некоторое время надпись в консоли исчезает, поскольку сборщик мусора отработал.
К сожалению, относительно функции прослушки это не так. Вот я создал демку. При удалении слушателя не удаляется функция. Только вручную, поскольку ссылка на слушатель (переменная
output, сохраненная в замыкании) так и хранится в памяти. В консоли постоянная ошибкаCannot set properties of null (setting 'textContent'). Что полностью совпадает с поведением самой первой реализации.Если удалить стор, слушатели удалятся в обоих случаях.
За использование EventTarget отдельный респект и уважуха )
Не пойму, а чем это принципиально отличается от самой первой реализации? Эмиттер неявно будет хранить все слушатели. Остальные проблемы там и остались вроде... Ну и вместо
store.value = "abc"нужно будет писатьstore.set({value: "abc"}).Вы имеете в виду CustomEvent? Если да, то их нужно диспатчить на уровне window (или document), поскольку потребители могут находиться вообще в разных местах DOM-дерева, и эти события не поймаешь на всплытии/погружении. Поэтому при удалении потребителя, нужно будет принудительно очищать слушатель, чтобы не засорять память. Поэтому где-то хранить слушатели все-таки придется.
1) а есть реализованный пример нескольких страниц, чтобы можно было увидеть, как там подгружаются скрипты и стили при переходе по ссылкам? Что-то у меня подозрение, что если сделать все по-уму, там будет далеко не один экран..
2) ну.. я бы не сказал, что это прям вот декларативно ) Кроме того, с таким подходом очень резко ограничивается область применения Fusor. Опять же непонятно, чем же это лучше тех же веб-компонентов, если фактически update вызывается императивно.
Особо нет времени смотреть на код. Просмотрел только бегло. Сразу возникли вопросы.
1) я вижу, что в примере роуты у Вас через #. Значит ли это, что вы сразу полностью грузите все приложение со всеми страницами? Или это только для примера в codepen?
2) разве в вашем фреймворке (то, от чего я призываю отказаться) можно вставлять теги как есть? Или только через вашу абстракцию?
3) если все-таки нормальные теги (элементы) вставлять можно, то как вы намерены обрабатывать случаи, когда эти элементы исчезают из DOM? Допустим, элемент уничтожен каким-либо способом. Как в таком случае отследить этот момент и очистить обработчик, чтобы ссылка на этот элемент в обработчике не препятствовала работе сборщика мусора?
Логично. Добавил пример. https://grigorenkosergey.github.io/native-SPA/pages/dynamic/12 Правда, реализовал на коленках, может что-то еще упустил.
Может потом внесу UPD в статью.
По приоритетам.. Если высчитывать совпадение с от наиболее специфичного к наименее специфичному, то особых проблем быть не должно.
На MDN постоянно вижу, когда перехожу по страницам. Несмотря на то, что там стили и js полностью берутся из кеша.
Ну.. Мне тоже сходу в голову не приходит ) Но я уже говорил об этом, что
"React со своим роутером нанес непоправимый урон моей психике". Потом проведу чуть более полное исследование этого вопроса.
Нет. Посмотрю как-нибудь.
Ну да, как вариант. Только потом TS прикрутить сложно будет без сборщика. Ну и я именно исходные htm/css файлы использовал для того, чтобы автодополнение, которое дает emmet из коробки работало безупречно. Например, такие штуки .parent>.child*5.
Честно говоря, уже не помню сути... Помню, что там внутренний сервер, который поднимается при разработке, работает совсем не так, как должен работать реальный сервер после деплоя (или как я изначально ожидал).
Вроде еще были проблемы при предобработке html - файлов... Ну а может дело во врожденной кривизне рук )
Насчет необходимости роутера.. Здесь вопрос, конечно, спорный. Я его сделал для возможности разработки именно приложений, в которых есть какое-либо сохраняемое состояние. Динамические роуты - здесь вопрос добавления нескольких регулярок и небольшой доработки. Я не стал с этим заморачиваться по причине отсутствия необходимости.
Ну и js выполняется не мгновенно. До того момента как JS отработает и отрисуются наши веб-компоненты пройдет некоторое время. Поэтому могут быть мигания. Когда же в окне компоненты уже зарегистрированы - такой проблемы нет.