Комментарии 108
https://github.com/balajahe/balajahe.github.io/blob/master/real_agent/obj-list.js
import * as WcMixin from '/WcApp/WcMixin.js'
const me = 'obj-list'
customElements.define(me, class extends HTMLElement {
connectedCallback() {
WcMixin.addAdjacentHTML(this, `
<style scoped>
${me} > #listDiv {
flex-flow: column;
margin-bottom: var(--margin2);
}
${me} obj-list-item {
min-width: 100%;
margin-top: var(--margin2);
padding-bottom: var(--margin1);
border-bottom: 1px solid silver;
flex-flow: column;
overflow: auto;
cursor: pointer;
}
${me} #objDesc {
display: block;
padding-left: var(--margin2);
}
${me} #objProps {
display: flex; flex-flow: row wrap;
padding-left: var(--margin2);
}
${me} > footer {
text-align: center;
padding-left: 1rem; padding-right: 1rem;
}
</style>
<div w-id='listDiv'></div>
<footer>Real Agent is a database of arbitrary objects with geolocation, photos and videos.</footer>
`)
APP.forceSetHash(me)
this.appBar = [
['sep'],
['new', async () => {
let mman = document.querySelector('#obj-new-medias')
if (!mman) mman = document.createElement('media-manager').build(
[],
[
['msg', 'Take photo, video, or audio:'],
['back'],
['next', () => APP.route('obj-new')]
]
)
APP.route('obj-new-medias', mman)
}]
]
this.addEventListener('save-item', async (ev) => {
await this.saveExistObj(ev.val)
this.querySelector('#' + ev.val.created).replaceWith(document.createElement('obj-list-item').build(ev.val))
})
this.addEventListener('delete-item', async (ev) => {
await this.deleteObj(ev.val)
this.querySelector('#' + ev.val).remove()
})
this.refreshList()
}
refreshList() {
this.listDiv.innerHTML = ''
APP.db.transaction("Objects").objectStore("Objects").openCursor(null,'prev').onsuccess = (ev) => {
const cursor = ev.target.result
if (cursor) {
this.listDiv.append(document.createElement('obj-list-item').build(cursor.value))
cursor.continue()
}
}
}
async addItem(obj) {
await this.saveNewObj(obj)
this.listDiv.prepend(document.createElement('obj-list-item').build(obj))
}
async saveNewObj(obj) {
const origins = []
for (const media of obj.medias) {
media.created = obj.created + media.created
origins.push({ created: media.created, origin: media.origin })
media.origin = null
}
const tran = APP.db.transaction(['Objects', 'Origins'], 'readwrite')
const proms = []
proms.push(new Promise(resolve => tran.objectStore("Objects").add(obj).onsuccess = resolve))
for (const origin of origins)
proms.push(new Promise(resolve => tran.objectStore("Origins").add(origin).onsuccess = resolve))
await Promise.all(proms)
}
async saveExistObj(obj) {
const origins = []
for (const media of obj.medias) {
if (media.origin) {
media.created = obj.created + media.created
origins.push({ created: media.created, origin: media.origin })
media.origin = null
}
}
const tran = APP.db.transaction(['Objects', 'Origins'], 'readwrite')
const proms = []
proms.push(new Promise(resolve => tran.objectStore("Objects").put(obj).onsuccess = resolve))
for (const origin of origins)
proms.push(new Promise(resolve => tran.objectStore("Origins").put(origin).onsuccess = resolve))
await Promise.all(proms)
}
async deleteObj(id) {
const tran = APP.db.transaction(['Objects', 'Origins'], 'readwrite')
const proms = []
proms.push(new Promise(resolve => tran.objectStore("Objects").delete(id).onsuccess = resolve))
proms.push(new Promise(resolve => tran.objectStore("Origins").delete(IDBKeyRange.bound(id, id + '_'.repeat(id.length))).onsuccess = resolve))
await Promise.all(proms)
}
})
Я даже не знаю что и сказать не используя матных слов, в принципе этот код сам за себя говорит) Представляю когда проект написанный вот так попадется в чьи-то руки на доработку, вот кто-то обрадуется))
Мы не в детском саду, чтобы оперировать понятиями "нравится / не нравится".
Конкретно этот код плох следующим:
- В глобальном неймспейсе (имена веб-компонент) используются короткие конфликтоопасные имена.
- insertAdjacentHTML довольно медленная штука.
- В строковых литералах не работает подсветка синтаксиса, автодополнение, переход к объявлению, поиск всех использований, авторефакторинг, проверка типов и тд.
- Ручное дёрганье обновлений плохо масштабируется по мере роста числа состояний. В итоге вы придёте либо к полному обновлению всего на свете по любому чиху, либо баги вида "тут что-то недообновилось" будут вашими вечными спутниками. По мере роста приложения вы неизбежно дойдёте до состояния "90% времени тратится на фиксы подобных багов и лишь 10% на новые фичи".
- Хранить данные в DOM и вообще взаимодействовать в DOM по каждому чиху — это верный путь к тормозам, как только приложение станет чуть больше, чем пара простых форм и список на 10 элементов.
- Ну и, конечно, объединение в кучу всего от стилей до работы с базой приводит к куче лишней копипасты, на поддержку которой будет требоваться всё больше и больше времени. Забавно видеть в статье упоминание SOLID и тут же нарушение первой же буквы S.
Все что вы перечислили, имеет место быть. Но как только вы устраните все эти проблемы, у вас появятся другие, например количество файлов в проекте, и вообще кода — утроится. Покажите мне совершенный реактивный код подобного приложения. Можно и бенчмарк сделать, на предмет медленного DOM.
Покажите мне совершенный реактивный код подобного приложенияВот это вы зря!
Покажите мне совершенный реактивный код подобного приложения.
https://github.com/hyoo-ru/notes.hyoo.ru
Можно и бенчмарк сделать, на предмет медленного DOM.
Не по теме поста, но на дисплее 1600x900 оказывается невозможно удалить заметку, потому что кнопка удаления не влезает, а горизонтальная прокрутка не работает
А какой браузер/ось у вас? Можно скриншот?
Гифка с тщетными попытками проскроллить https://habrastorage.org/webt/tw/jp/zq/twjpzqqlmdgljgionq3zwir2im0.gif
Спасибо, мол вижу впервые, вечером изучу !
Такое ощущение что вы не умеете готовить реактивный код)
Извините за, возможно, неуместный вопрос. Но в чем проблема количества файлов в проекте, когда они удобно организованы?
В этом коде перемешано создание UI и логика работы с данными. Это плохо, потому что не позволяет менять одну часть, не трогая другую.
Кроме того, а как вы тестируете компоненты написанные таким образом?
Что конкретно вам не нравится?
Найболее отвратительно — полное отсутствие статики. Куча каких-то непонятных строк, которые вызывают строки и которые возвращают строки. Что это за кошмар?
APP.db.transaction("Objects").objectStore("Objects").openCursor(null,'prev')
А также «sep», «new», «msg», «obj-new-medias» и так далее — строки, строки, строки, строки. Все одинаковые, все ничего не говорят, все захардкоджены прям внутри кода. Некоторые ещё и повторяются несколько раз.
Как только я такое вижу — помечаю код как жуткое легаси с которым запрещено работать до полного и нормального переписывания.
Грамотное проектирование в принципе не допускает появление подобного кодаНу это ведь тоже слишком абстрактно. Какого «такого». Я понимаю, что код плохой и так писать нельзя, но какие яркие признаки есть того, что код плохой?
Все короткие имена — локальные, внутри класса. Кроме собственно имен веб- компонентов, которые через дефис, на это я повлиять не могу — стандарт.
api github это хороший пример
Вот не сказал бы. Они вываливают клиенту 100500 полей, часть из которых дублируется. А потом прикрывают это дело довольно жёсткими лимитами.
Если WebApp проект стартует сильный разраб, то понятно что он возьмет $mol, а если слабый, то понятно что React. Но это же не значит что сильные разрабы должны равняться на слабых и преднамеренно лишать себя крутых благ и стартовать проект на дерьмовом стеке)
Ох уж этот парадокс Блаба.
Почему не effector тогда, а сразу mobx ?)
Первые впечатления на пэт проекте от него неплохие. Возможно, вы давно его пробовали?
Но спасибо за ваш ответ — попробую и mobX.
Ваше мнение насчёт context.provider? Во флаттере форсится почти как стандарт, почему в реакт нельзя использовать только его ?
Спасибо, понятно. Поэтому я и не люблю реакт, слишком много нюансов и противоречивых подходов. Посмотрите как чисто то же самое реализовано на флаттере, который некоторые считают форком реакта. Теперь на нем и вебню можно писать:
https://m.habr.com/ru/company/piter/blog/503074/
class MyBloc {
final _controller = StreamController<MyType>(); Stream<MyType> get stream => _controller.stream;
StreamSink<MyType> get sink => _controller.sink;
myMethod() {
// ВАШ КОД
sink.add(foo);
} dispose() {
_controller.close();
}
}
Пример виджета, потребляющего BLoC:
@override
Widget build(BuildContext context) {
return StreamBuilder<MyType>(
stream: myBloc.stream,
builder: (context, asyncSnapshot) {
// ВАШ КОД
});
}
Что бросилось в глаза —
export default
— лучше не стоит, а то во время рефакторинга на это хорошо так напороться можно.Вот здесь можно подробнее ознакомиться.
имя можно поменять при импорте, или разные модули могут экспортировать одинаковые имена
Вот только это происходит явно, чего не скажешь об export default
Нормальная IDE сама ищет, показывает и заменяет.
Что ей мешает искать дефолтные импорты?
Вот только это происходит явно, чего не скажешь об export default
Что вы подразумеваете под словом «явно»?
import Component from './Component';
Вполне явно импортирует с именем Component.
Явно — именованый экспорт. Примеры имеются по ссылке, что выше скидал.
Напоретесь разок — поймете.
Но желаю вам никогда с таким не сталкиватся!)
По ссылке всё прочитал, но не понял, о каких проблемах с рефакторингом вы говорите.
Напоретесь разок — поймете.
Хотелось бы понять, на что я могу напороться до того, как это произойдёт.
Не знаю, как лучше донести смысл, который я пытаюсь вложить в мои сообщения.
Экспорт по-умолчанию делает невозможным крупномасштабный рефакторинг, так как импортируемый модуль может называться по-разному в каждом месте (включая опечатки).
Какая разница, как называется импортируемый модуль, если IDE ищет семантически? К тому же, именованный импорт тоже может называться по-разному.
// true.js
<...>
export default true;
// app.js
import false from 'true';
import true from 'false';
// happy debugging!
И да, я безусловно согласен с вами, что IDE как раз с этим неплохо разберется. А вот люди читающие код — уже гораздо хуже.
И по этой ссылке, также не объясняется, как дефолтный экспорт мешает рефакторингу.
Если вы через рефакторинг переименовываете класс в одном месте — он не переименовывается во всех остальных. Как результат переименование часто используемого класса становится ужасным испытанием.
Использование именованных экспортов не решает эту проблему полностью. Импорты с "as" тоже придется править руками. И на практике, скорее всего, их просто забудут исправить. Вместо решения, вы бежите от проблемы, создавая новые.
Полный путь к файлу создает уникальный скоуп для всего содержимого. Всё в файле должно именоваться относительно этого контекста. Тавтология не создаст глобально уникальных имен (если только вы не в несете полный путь в каждое имя).
Использование именованных экспортов не решает эту проблему полностьюОно решает её достаточно
Импорты с «as» тоже придется править руками
Ну, во-первых, таким импорты будут явно указаны, плюс в начале файла можно будет увидеть родное название.
Во-вторых, у нас не принято использовать as.
Вместо решения, вы бежите от проблемы, создавая новые.Какие именно «новые» проблемы созданы? Это проблемы старые — с дефолтными импортами они остаются.
Тавтология не создаст глобально уникальных имен
А нам и не нужны глобально уникальные имена. Важно писать так, чтобы они были уникальные в местах, где используются. То есть не импортировать в файл два класса с одинаковыми названиями из разных мест.
Оно решает её достаточно
В таком случае её можно было и не решать вообще. Переименовывание и так достаточно редкий кейс. И, повторюсь, при вашем подходе, вы с большой вероятностью забудете поправить `as` импорты, потому что будете полагаться на IDE.
Ну, во-первых, таким импорты будут явно указаны,
Опять это слово «явно». Чем `import Name from` неявен? Тут явно написано, что объект, экспортируемый по дефолту, нужно импортировать с именем Name.
плюс в начале файла можно будет увидеть родное название.
И в дефолтном импорте можно увидеть название файла.
Какие именно «новые» проблемы созданы?
Отсутствие строгой системы в именовании.
Важно писать так, чтобы они были уникальные в местах, где используются.
Т.е. вы про каждый экспорт должны думать, а где и с чем вместе он будет использоваться? Экспорт — такое же локальное имя, как и всё остальное в файле. Он находится в том же смысловом контексте, и должен именоваться аналогично со всем остальным, без добавления контекста в имя. Иначе возникает неконсистентность в именах, которая всё равно не избавит от коллизий имён, а значит `as` придётся использовать, и все проблемы никуда не деваются. Вы не решили проблему, а замели пыль под кровать.
Главная проблема при работе с контекстом — это поиск shared state и общего предка. Как нам упростить эти шаги? Так как у нас реакт-дерево — это дерево, то у всех компонентов есть общий предок. Давайте хранить все shared state в root'овом компоненте.Это слишком радикальный прыжок разума ) Поиск общего предка — это не проблема, это задача, если мы хотим слепить приложение из чужих компонентов, или хотим компоненты переиспользовать многократно, или хотим их менять в процессе эксплуатации, короче когда нам нужен локальный стейт с понятным временем жизни.
Вообще меня впечатлил effector и его философия мультисторов, где в принципе нет проблемы с локальным стейтом, да и общие предки в нем не нужны.
Вот ещё смотрю mobx — пока только изучаю, ничего касательно него сказать не могу.
Напомните, какие сейчас лучшие практики для серверного рендеринга приложения, использующего web components?
Нет таких, сейчас веб-компоненты не поддаются SSR. Есть некоторые наработки по гидратации в lit-html
, есть @skatejs/ssr
, но, насколько я знаю, это всё пока на ранних экспериментальных стадиях.
Сейчас всё упирается в императивность создания Shadow Root. Есть пропозал на его декларативность, но он пока не особо куда-то движется.
А какие реактивные технологии вы пробовали? Упомянутые реакт и редакс к реактивности не имеют никакого отношения.
Относитесь к redux-like стейту не как к централизованному хранилищу состояния компонентов, а так же, как к СУБД на бэкенде. Никто же в здравом уме не пишет sql-запросы в Presentation слое. Тот же подход сработает и здесь.
1. Как сделать разный UI для разных девайсов? Например, для десктопа показать форму простыней, для мобильных ту же форму в виде визарда. В вашем случае придется переписывать не только вью, но и всю логику.
2. Как одним махом сохранить текущее состояние приложения? Например, при логауте сохранить при следующем логине вернуть пользователя в то состояние, где он нажал логаут.
3. Как делать переходы от экрана к экрану без перезагрузки страницы? Если логика зашита в DOM вам придется при каждом переходе восстанавливать нужное состояние.
4. Сложности при переносе наработок от одного проекта в другой. Довольно часто, одинаковая функциональность имеет очень разное представление от проекта к проекту.
Наконец-то сильные аргументы !
1) Отвязать DOM от HTML-верстки, не должно быть такого, что при смене диагонали у вас иерархия компонентов поползла. Такая отвязка даже на уровне CSS работает — флексбокс хотите в столбик, хотите в ряд. Визард vs простыня — согласен, придется полностью переписать. Но это и так почти все делают, тот же мобильный Хабр, насколько я понимаю, другой. Да и в визарде логикика переходов будет другая, значит и стейт. Может кто-то и делал две версии на одной кодовой базе, я так не умею ).
2) Только писать свой сериализатор, технически браузер позволяет весь DOM в переменную JS сохранить, а потом обратно восстановить в document, но во-первых переменную эту штатно не сериализовать, во вторых все равно слетают значения контролов формы, так что тут реактивность бесспорно выигрывает.
3) Скрывать и отображать дивы. У меня сделаны переходы и в стиле визарда (при создании новой карточки) и в стиле стека (при редактировании старой карточки). Зайдите и посмотрите как меняется location.hash при обеих типах навигации. Поэтому для возврата назад или закрытия модальной формы мне достаточно сказать history.go(-1), и все присходит автоматически — модальная часть DOM уничтожается, а линейная просто скрывается, отображая предыдущий DIV. Тут как раз большой плюс, что сохранено все, включая контролы и позиция скролинга. Да и навигация быстрее в разы получается.
2) Весь DOM в переменную JSON, это вы хватили)
3). Как быть если пользователь напрямую по ссылке зашел на какую-то промежуточную страницу и history.go(-1) никуда не ведет? Тут как раз, механика роутов которая есть во всех популярных библиотеках очень вам поможет.
— «тотально доминирует реактивность» — совсем не тотально, только в наиболее продвинутых с технической точки зрения проектах.
— «проблем вроде централизованного хранилища данных» — проблемой является по большей части как раз распределенное хранилище, разбросанное по разным компонентам и базам. Централизованное же предоставляет удобную возможность обращаться за актуальными данными из любого места, делать «слепки состояний», точно знать какими данными оперирует приложение и удобно рефакторить.
— «почему собственно данные должны храниться в объектах JS, а не напрямую в узлах DOM?» — концепт переноса операций с данными и хранилищ в JS — молодой, до этого состояние хранили как раз в дата-атрибутах и была масса фреймворков с подобной схемой работы. Однако медленность, необходимость постоянной сериализации-десериализации и работа по неудобному DOM-апи (вместо js-методов) неизбежно привели к выводу, что разрабатывая на JS, нужно там же и хранить данные, а в браузерное представление трансформировать скрытым реактивным механизмом как сайд-эффект. Присопособленность терпеть недостатки выдает в вас олдскул, так как раньше действительно технологии не позволяли подобного)
— «подобная реактивность вообще не часто востребована» — даже не могу найти примеров, где не востребовано подобное удобство. На работу можно доехать и на «запорожце» (вручную работая с DOM), но других людей (команду) возить стыдно.
— «функциональщина + виртуальный DOM — вещи не бесплатные» — не знаю что значит «функциональщина», но сравнить пару объектов явно дешевле работы с DOM.
— «компоненты, управляемые разными реактивными библиотеками» — если у вас есть такое в проекте, выбрасывайте, а не ищите способы скрестить ужа с ежом.
Примеры кода даже комментировать не хочется, в 2005 мне бы понравился — прямо как в учебниках, createElement, prepend-append, event-driven модель передачи событий, верстка в строках… Для первого проекта — самое то, но не верю, что серьезно топите за это все.
Спасибо за комментарий!
PS
Вернуться в олдскул меня побудила печаль с производительностью неудачных реактивных проектов. Императив оказался быстрее. Возможно я плохо готовил реакт, и нужно вернуться к снаряду ещё раз.
В этом смысле олдскульные практики экономить каждый килобайт и делать css-only-интерактив (модалки, слайдеры и тп) вместо js, да и еще nojs-версии, вызывают добрую ностальгию. Но подойдите к снаряду еще пару раз — я привел реальные крупные примеры, но каждый из них удалось сокращать раза в 3 и делать удобным в разработке и быстрым как раз благодаря современным практикам реактивности.
2.3 мегабайта мимифицированного JS, побитого на 100 файлов. Приложение просто делает фотки и кладет в базу, у меня функционал в три раза больше, а размер исходного JS — 27 килобайт. Если б на тайпскрипте сделал — было бы 15 килобайт.
Разница в 85 раз, Карл ! И это еще без редакса с мобиксом.
Для сравнения — на флаттере приложение 1.5 мегабайта одним файлом.
Я ничего не имею против реакта с иоником, и даже наверное буду продолжать на них писать, но это явно инструменты для богатых. Я понимаю, функциональный код проще тестировать, меньше ошибок и т.д. Но линукс написан на голом СИ, и до сих пор работает )
PS
Объем исходного кода одинаков — что на ионике, что на ванили, на ионике даже немного больше за счет длинных имен.
Пока что, на мой взгляд, если уж хочется протащить что-то в достаточно серьезный продукт (в «наколенное» и мелкое — вебкомпоненты скорее всего зайдут без проблем), то лучше брать цельное вендорское решение а-ля Svelte, чем опираться на несколько сырых стандартов сразу (webcomponents, shadow DOM), плюс еще и синтаксически кривого монстра JSX.
ЗЫ: Хотя конечно Svelte тоже собственный синтаксис изобретает, и это тоже такое себе. Короче, нет счастья.
Состояние веб-приложения — это переменные JS, а DOM является лишь отображеним, которое нужно уметь «умно» пересоздавать. Идея хорошая, но почему собственно данные должны храниться в объектах JS, а не напрямую в узлах DOM
Потому что DOM для этого не предназначен, и хранить там можно только строки?
однако к сожалению функциональщина + виртуальный DOM — вещи не бесплатные
Бенчмарки предоставьте пожалуйста.
Функция querySelector() представляет собой великолепное API для адресации компонентов
А ничего что для ее использования надо сканировать документ на каждую операцию, что на порядки дороже прямой адресации по известной ссылке? А то что idшники должны быть UUID, иначе не избежать конфликтов?
Код из подобной лапши перестает быть поддерживаемым сразу, как только там становится больше одного дива.
Реактивный код можно вполне писать без блевотной редуксо-лапши, посмотрите например на тудушку на Scala.js github.com/Karasiq/scalajs-bootstrap/blob/master/test/frontend-v4/src/main/scala/com/karasiq/bootstrap4/test/frontend/TodoList.scala ( karasiq.github.io/scalajs-bootstrap/index-v4)
Хороший функциональный код легко понятен и правится даже через 20 лет, потому что имеет совершенно очевидный «flow» и лишен мусора из абстракций, наследования, фабрик фабрик фабрик и т.п., и при этом не ломается от изменения одной строчки или значения переменной как императивная лапша.
Может вам svelte попробовать? Компилируется в чистый js, компоненты есть и они сразу js+html+css, никакого оверхеда на shadow dom / event loop / etc.
функциональщина + виртуальный DOM — вещи не бесплатные, они существенно нагружают и процессор и сборщик мусора. Иначе бы не придумали SSR.автор это бред. ssr создан, чтобы быстро показывать контент в виде html+seo. Загрузка скриптов и их парсинг требует времени, особенно на мобильниках. Если вас не устраивает фунциональное программирование, глобальный store и виртуальный DOM — angular + mobx
Идея хорошая, но почему собственно данные должны храниться в объектах JS, а не напрямую в узлах DOM?
Потому что JS, благодаря JIT-компиляции — довольно быстрый, а DOM — всегда медленный. Храня данные в JS, с ними можно успевать делать в десятки и сотни тысяч раз больше действий, не вызывая видимых просадок производительности относительно DOM.
Разумеется, если у вас страница «просто показывает данные», причём очень небольшое их количество за раз — храните всё в DOM, оно будет иметь право на жизнь. Важно только помнить, что такая ситуация не у всех и не везде.
Мне же нужна реактивность компонентов, а не реактивность HTML.
А мне вообще нужна реактивность моделей, потому что имея её — можно далее относительно несложно подключиться к любому ЖЦ любых компонентов, реакт это или не реакт (причём, кстати, к реакту подключаться наверное сложнее всего, спасибо его собственным загонам про виртуальный DOM).
Главная соль реактивности вообще не на уровне view, а именно на уровне model и controller.
Иначе бы не придумали SSR.
Главный драйвер SSR — это три магические буквы, среди которых есть буква S, буква E, и буква O. А не соображения производительности, это-то как раз вторично.
В проекте все компоненты, как визуальные, так и для работы с данными, реализованы в виде классов. Визуальные компоненты расширяют либо HTMLElement, либо специализированные стандартные компоненты. При этом конструкторы визуальных компонентов содержат ссылку на корневой объект приложения, в котором в Map-кеше хранятся все визуальные компоненты.
Идея регистрации компонентов в кеше, заключается в том, что доступ к объектам осуществляется по ссылке, и если в качестве ключа в Map использовать имя переменной компонента, то доступ к любому компоненту из любого другого компонента можно получить практически мгновенно путем вызова
this.#app.getComponent(compName)
вместо использования медленного метода document.querySelector(appSelector)
.Регистрация компонентов в приложении, которым является объект класса App, определенный в файле index.js, производится с помощью вызова следующего метода:
addComponent(componentName, component) {
this.#components.set(componentName, component);
}
В свою очередь внутренняя переменная #components определяется в конструкторе класса App как
this.#components = new Map()
, которая выполняет роль глобального пространства имен с предельно быстрым доступом к объектам по имени их переменных. Еще одна оптимизация связана с тем, что самая быстрая обработка строк является на порядки медленнее самой неоптимизированной работы с объектами. Поэтому строки с формулами в момент ввода сразу разбиваются на токены, для каждого из который создается экземпляр класса Token, массив которых также сохраняется в Map-кеше, в котором ключами выступают имена ячеек, содержащих формулы.
В целом идея таких подобных оптимизаций заключается в том, чтобы максимально избегать работы со строками, а также сохранять в Map-кешах ссылки на объекты для быстрого доступа к ним. В общем, если что сказал не так, то не судите строго начинающего JS-писателя.
Вот и возникла идея — а давайте будем размещать и модели и вьюшки в едином дереве компонентов с автоматической деструкцией. Получился паттерн контекст-провайдер, наиболее красиво реализованный во Flutter, ссылку на хорошую статью уже приводил habr.com/ru/company/piter/blog/503074
Но тогда получается, что моделька — это просто дополнительный узел в дереве виджетов, и грань между виджетом-моделью и виджетом-представлением сильно истончается. А следующим логическим шагом будет объединение этих 2-х виджетов в один «толстый» компонент, как это сделано в моем коде.
Ну то есть в индустрии существует вечный спор — на какие части нарезать изначальную сложность бизнес-задачи. Можно по технологическим признакам (хранилище — модель — контроллер — вью), можно по бизнес-ответственности (справочник клиентов, операция закупка, операция продажа), а больших приложениях всегда режут и так и так — горизонтально по технологиям, вертикально по функционалу, называется матричная схема. В относительно компактных проектах достаточно оставить только вертикальное деление, то есть толстые автономные однофайловые компоненты. Это уменьшает количество сущностей, файлов, и служебного кода. И делает приложение более живучим, когда каждый компонент содержит внутри логику собственного сториджа, реконнекта к другим компонентам, визуализации и проч.
Подобный подход (толстые автономные компоненты) в свое время был популярен на бэке, называется микросервисы. Каждый микросервис автономен, живуч, и имеет собственную БД. Отдельная БД на сервис, по сути инкапсуляция сториджа внутри сервиса — это выглядит странно (всегда было принято уровень хранения выделять в отдельный слой приложения), но тем не менее куча людей эти микросервисы форсят (я нет). Так же и толстые веб-компоненты имеют право на жизнь в определенных случаях. Причем, такие толстые model-view компоненты внутри могут быть сколь угодно сложными и реактивными, но извне выглядят как монолит.
С другой стороны, смешение в одном компоненте всевозможного функционала с какого-то момента резко ухудшает возможности масштабирования проекта. Даже если разработчик один. Просто в силу ограничений человеческого мозга. Одно дело, когда в одном классе присутствует код, решающий конкретную задачу. И совершенно другое дело, когда в одном классе присутствует самый разнообразный код. В этом случае как минимум нужно постоянно держать в голове, с какими функциями нужно работать сейчас, а на какие не нужно обращать внимания.
Да, инкапсуляция кода добавляет как минимум один дополнительный уровень абстракции. Если приложение небольшое, что эта абстракция на начальном этапе выглядит явно избыточной. Но в жизни аппетит приходит во время еды. И если изначальная идея приложения оказалась востребованной, то постепенно появляются все новые хотелки по расширению функционала. При изначально модульной архитектуре добавление нового функционала представляет собой просто рутину. В случае же монолита даже самая мелкая доработка представляет собой самый настоящий головняк с непредсказуемыми последствиями от внесения даже небольших изменений в код.
Ну и лично по своим предпочтениям отмечу, что для меня в приоритете находятся скорость и масштабируемость проекта. При этом сложность проекта может быть сколь угодно высокой, но эту сложность по возможности нужно инкапсулировать в классы с понятным интерфейсом, а внутри класса писать понятный код без избыточных сокращений и делать комментарии ко всем более-менее логически сложным фрагментам кода.
Ну и да, по поводу внешних библиотек. Лично я придерживаюсь разумного компромисса между полным контролем кода, когда пишется все свое, и использованием простых и надежных внешних библиотек. Этот компромисс исходит из затрат времени на длинном горизонте планирования. С одной стороны, на начальном этапе нужно потратить много времени на написание собственных библиотек, но затем можно практически не вспоминать про них. С другой стороны, быстрое подключение сторонних библиотек со временем будет требовать все большего времени на изучение появляющихся новых возможностей и ошибок в этих библиотеках с необходимостью последующей адаптации собственного кода.
Реактивность — это не технология, это простенький паттерн, который юзали все и везде, до того как обхайпованные придумали ему название. Называть это технологией, всеравно что называть технологией 2+2=4
И на Ассемблере сейчас пишут, и на языках высокого уровня
Если есть группа среднего уровня спецов — берется какой-нибудь фреймворк
Если один профессиональный программер — можно писать код ванильно и эффективно (а не олдскульно, как выше обозначили)
Какие проблемы?
Для больших расширяемых командных проектов фреймворки однозначно удобней.
Мощности машин позволяют терять 99% времени CPU и памяти именно на сопутствующих фреймворкных процессах, а не бизнес-логике приложения.
Когда ты едешь на автомобиле из точки А в точку Б, ты тоже тратишь 95% энергии топлива на по сути бесполезное перемещение груды железа, а не тебя.
можно писать код ванильно и эффективно (а не олдскульно, как выше обозначили)Это как? В приведенном коде всего 2 проблемы — совмещение логики сториджа с логикой отображения в одно толстом компоненте (хотя последняя тенденция в том же Vue — делить не по технологиям а по ответственности, отсюда и однофайловые компоненты), и использование строк вместо статики. Для маленького приложения это норм. Логика работы с БД выносится на узел выше, по аналогии с Flutter Provider или реактовым useContext() за 10 минут, не ломая при этом всю архитектуру. При этом у меня получился несжатый бандл в 18.5 раз (!) меньше чем упакованный бандл стартового приложения React. Плюс при навигации страницы не рендерятся заново, поскольку целиком хранятся в DOM а не в State, а это означает лучшую отзывчивость.
PS
Я работал на действительно больших проектах, когда в БД полторы тысячи таблиц. Большинство коммерческих веб-приложений примерно в 10 раз меньше. И да, в том большом проекте не было декларативщины и реактивщины — вполне себе олдскульный императивный ООП.
PPSS
Я люблю фукнциональное программирование, но деньги приходится зарабатывать ООП, увы, потому что приходится работать на высоко-нагруженных проектах.
PPPSSS
Сейчас активно изучаю Flutter с прицелом в том числе на WEB-разработку, и бандл у него на маленьких приложениях где-то в 2 раза больше реактового. Кодирование абсолютно в стиле React )
Сейчас принято считать, если ты не используешь популярные фреймворки, то это олдскульно (устаревше), хотя, возможно, тебе просто нужна производительность (эффективность)
Реактивные веб-технологии излишне переоценены