[Разработка веб-сайтов, JavaScript, HTML, Angular, ReactJS] Карго-культ HTML в современном фронтенде
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Здравствуйте, меня зовут Дмитрий Карловский и я… люблю рвать шаблоны. А во фронтенде как раз крайне много заблуждений вокруг шаблонизации. Так что давайте порвём их на лоскуты снизу вверх и справа налево.
Далее мы разберём что такое шаблоны. Их ключевые достоинства и фатальные недостатки. Зачем они нужны и почему не нужны. Сформируем представление о правильном решении и проедемся катком по популярным. Так что полная гамма чувств нам обеспечена.
Прошу к столу..
А что такое шаблон?
Казалось бы, тут всё очевидно: это способ генерации кода на целевом языке. Однако, всё не так просто. Чтобы понять ключевое свойство шаблона, давайте рассмотрим пару примеров..
Это шаблон:
"Hello, ${name}!"
А это уже нет:
"Hello" + name + "!"
Оба кода делают одно и то же, но почему одно является шаблоном, а другое — нет? Всё дело в том, какой язык является первичным в коде, а какой является опциональной добавкой к нему. Шаблон предполагает написание кода сразу на целевом языке, вкрапляя в него специальные управляющие конструкции, которые тем не менее синтаксически согласованы с целевым языком.
Яркий пример синтаксически согласованных управляющих конструкций можно наблюдать в XSLT:
<xsl:template name="page">
<acticle>
<h1>
<xsl:copy-of select="./head" />
</h1>
<xsl:copy-of select="./body" />
</article>
</xsl:template>
А вот такой код, не смотря на использование шаблонов в 1 и 3 строке, в целом шаблоном всё же не является, так как чтобы понять, каков будет результат, нужно мысленно корректно исполнить JSX-код:
const head = <h1>{ headContent }</h1>
const body = 'Hello, World'
const article = <article>{ head }{ body }</article>
Грубо говоря, если взять парсер целевого языка, то он справится и с парсингом шаблона, просто проигнорировав управляющие шаблонные конструкции, никак их не обрабатывая. И, соответственно, человек тоже может буквально видеть получающийся из шаблона результат, а не собирать его в уме.
Как видно в последнем примере, код на JSX может быть шаблоном, а может им и не быть. И как правило шаблоном он всё же не является, не смотря на синтаксическое подражание HTML.
А необходим ли HTML?
Во фронтенде целевым языком для шаблонов как правило является HTML. А HTML является не более, чем сериализованным представлением DOM дерева. И в прошлом именно HTML был языком коммуникации между клиентом и сервером. Поэтому серверу нужно было генерировать именно его.
Однако, в современном вебе клиент и сервер больше не обмениваются HTML, предпочитая JSON, ProtoBuf и другие более эффективные форматы. Более того, теперь клиент уже сам формирует DOM напрямую, через JS-API, минуя HTML представление. А это значит, что в качестве целевого языка описания DOM может быть использован не только HTML, но и иные форматы сериализации DOM.
Например, HAML:
!!!
%html{ :lang => "ru" }
%head
%title= title
%meta{ 'http-equiv' => 'Content-Type', :content => 'text/html' }/
%body
%h1= title
%p= description
Или xml.tree:
! DOCTYPE html
html
@ lang \ru
head
title ? title
meta
@ content \text/html; charset=utf-8
@ http-equiv \Content-Type
body
h1 ? title
p ? description
Или даже JSON. Без примера, ибо слишком уж он развесистый получается.
В этом свете использование HTML-шаблонизации является скорее данью традиции, чем реальной необходимостью:
<!DOCTYPE html>
<html lang='ru'>
<head>
<title>{title}</title>
<meta
content='text/html; charset=utf-8'
http-equiv='Content-Type'
/>
</head>
<body>
<h1>{title}</h1>
<p>{description}</p>
</body>
</html>
А достаточно ли HTML?
Мощности HTML хватает лишь для описания DOM. Но современная разработка предполагает компонентную декомпозицию. А где декомпозиция — там и композиция. То есть нам необходим инструмент для создания экземпляров компонент, их настройки и соединения друг с другом реактивными связями разных направлений.
Тут не то что HTML, а даже DOM уже катастрофически не хватает, что неизбежно порождает чудовищ. Например, вам нужно вставить несколько компонент и провязать их состояния друг с другом.
Возьмём ангуляровский "шаблон":
<bi-panel class="example">
<check-box
class="editable"
side="left"
[(checked)]="editable"
i18n
>
Editable
</check-box>
<text-area
#input
class="input"
side="left"
[(value)]="text"
[enabled]="editable"
placeholer="Markdown content.."
i18n-placeholder="Showed when input is empty"
/>
<div
*ngIf="text"
class="output-label"
side="right"
i18n
>
Result
</div>
<mark-down
*ngIf="text"
class="output"
side="right"
text="{{text}}"
/>
</bi-panel>
Весьма похоже на HTML, но только это не HTML, чтобы там ни говорили Angular-евангелисты. DOM (и как следствие HTML) поддерживают лишь задание строк в качестве атрибутов. А для компонент нужны не только строки, но и другие типы данных: числа, объекты и даже другие компоненты. И их надо не только хардкодить в шаблоне, но и брать из свойств, класть в свойства, а то и вообще обеспечивать двустороннее связывание.
И тут начинаются кастомные расширения HTML. Каждый атрибут в примере выше имеет свою семантику, но синтаксически выглядят они все одинаково:
- #input — это локальный идентификатор, для доступа через TS.
- class="editable" — это имя класса для привязки стилей через CSS.
- side="left" — это имя слота, куда этот элемент будет помещён.
- [(checked)]="editable" — это двустороннее связывание свойств вложенного и внешнего компонентов.
- [enabled]="editable" — это уже одностороннее.
- text="{{text}}" — а это тоже самое.
- placeholer="Markdown content.." — это какой-то захардкоженный текст.
- i18n-placeholder="Showed when input is empty" — а это, внезапно, указание, что атрибут placeholder подлежит переводу, и пояснение переводчику.
- *ngIf="text" — это же вообще к компоненту не относится, а регулирует будет ли компонент рендериться в родителе.
Все 4 компонента лежат вперемешку, не смотря на то, что часть из них относится к левому слоту, а часть к правому. То есть это мало того, что не HTML, так это ещё и вовсе не шаблон. Это — язык для компоновки компонент, мимикрирующий под HTML. Из-за этой мимикрии он преисполнен горой сомнительных решений, осложняющих изучение, разработку, чтение и поддержку как самого прикладного кода, так и инструментария, превращающего эти "шаблоны" во что-то, что может исполнить браузер, чтобы показать интерфейс.
Но чем же HTML хорош?
Ключевое достоинство HTML — его декларативность. Вопреки расхожему мнению, декларативные языки описывают на самом деле не "результат", а некоторую семантическую структуру. И эта структура может быть использована для программного анализа с получением множества разных "результатов" в зависимости от потребностей.
Мы можем взять HTML и нарисовать на экране красивый плоский интерфейс. Можем в VR показать объёмный интерфейс, который можно потрогать. Можем реализовать голосовой интерфейс для не зрячих. Можем распечатать в виде книги. Можем собрать все заголовки для формирования оглавления и все термины для тезауруса. Можем собрать все ссылки и уведомить сайты, куда они ведут, о том, откуда на них ссылаются. Можем отправить уведомление всем упомянутым пользователям. И много чего ещё.
Но всё это многообразие возможностей крайне затруднено, а то и попросту не возможно при написании императивного кода, который описывает конкретные действия по получению одного конкретного результата. Что бы там ни говорили адепты функционального программирования, но оно ни в коем разе не является декларативным. ФП — это просто императивное программирование на иммутабельных структурах, описывающее конкретные действия, по трансформации заданных входных параметров в заданные выходные. Всё, что можно сделать с чистой функцией — это её исполнить. А всё, что может дать нам анализ её кода — это "ну, там вызываются какие-то функции, вот их сигнатуры".
Простой пример императивного функционального кода:
приготовить_яичницу = ()=> последовательность(
()=> яйцо ,
яйцо => разбей( яйцо ) ,
разбитое_яйцо => уберать_скорлупу( разбитое_яйцо ),
яйцо_без_скорлупы => пожарить( сковорода )( яйцо_без_скорлупы ),
жаренное_яйцо => добавить_приправы( жаренное_яйцо )
)
А вот пример настоящего декларативного кода в модели RDF:
яичница
включает
жареное_яйцо
приправы
жареное_яйцо
создаётся_посредством
горячая_поверхность
скворода
является
горячая_поверхность
жареное_яйцо
создаётся_из
яйцо_без_скорлупы
яйцо
включает
яйцо_без_скорлупы
скорлупа
Это логические триплеты. Благодаря нормализованному представлению их очень просто парсить и анализировать.
Но вернёмся к нашим шаблонам. Возьмём популярный сейчас JSX, который мимикрирует не только под HTML, но и под JS, и даже под ФП, при этом ничем из упомянутого не являясь:
const Example = ( props: {
className?: string
text?: string
onTextChanged?: ( next: string )=> void
editable?: boolean
onEditableToggle?: ( next: boolean )=> void
} )=> {
const [ stateText, setStateText ] = useState( props.text ?? '' )
const [ stateEditable, setStateEditable ] = useState( props.editable ?? true )
const [ inputElement, setInputElement ] = useState< HTMLTextAreaElement >( null )
const className = ( props.className ?? '' ) + ' example'
const text = props.text ?? stateText
const editable = props.editable ?? stateEditable
const setText = useCallback( ( next: string )=> {
setStateText( next )
props.onTextChanged?.( next )
}, [ props.onTextChanged ] )
const setEditable = useCallback( ( next: boolean )=> {
setStateEditable( next )
props.onEditableToggle?.( next )
}, [ props.onEditableToggle ] )
return (
<BiPanel
className={ className }
left={
<>
<CheckBox
className="editable"
checked={ editable }
onToggle={ setEditable }
>
{ l10n( 'Editable' ) }
</CheckBox>
<TextArea
ref={ setInputElement }
className="input"
value={ text }
onChange={ setText }
enabled={ editable }
placeholder={ l10n( 'Markdown content..' ) }
/>
</>
}
right={
text
? <>
<div
className="output-label"
>
{ l10n( 'Result' ) }
</div>
<MarkDown
className="output"
text={ text }
/>
</>
: <></>
}
/>
)
}
Закроем пока глаза на объёмность и сложность кода. Давайте подумаем, что мы можем получить из него без исполнения..
Можем ли мы при сборке вытащить все локализуемые тексты и заменить их на персистентные ключи?
Нет. Даже если мы распарсим весь код в AST, найдём все вызовы функции l10n и понадеемся, что передан ей всегда будет лишь строковый литерал, а не какое-нибудь выражение, нам всё равно неоткуда взять персистентную информацию для формирования ключей, чтобы они не менялись при каждом изменении вёрстки.
Можем ли мы в визуальном конфигураторе понять, что свойства CheckBox.checked, TextArea.enabled и props.editable связаны друг с другом двусторонней связью?
Нет. И не верьте адептам Реакта, утверждающим, что двустороннего связывания там нет, и что оно вообще не нужно. Оно и нужно, и есть, хоть и реализуется через костыли с парными пропсами вида checked={ editable } onToggle={ setEditable }.
Можем ли мы там же понять, что если не задать свойство editable, то текстария будет изначально редактируемой?
Нет. Разве что очень сильно заморочиться и реализовать data-flow анализ. И то он будет справляться далеко не со всем многообразием возможного кода.
Можем ли мы при сборке проверить, что CSS-селектор .example .output .link действительно на что-то матчится?
Нет. Так как имена классов собираются из строк в прикладном коде.
Продолжать можно долго, но суть уже должна быть ясна: императивные языки содержат слишком мало информации о высокоуровневых абстракциях и слишком много низкоуровневого шума. Это капитально осложняет программный анализ.
А возможна ли декларативность?
Чем меньше язык позволяет вольностей, тем проще его программно анализировать. Но обратная сторона этого — снижение гибкости. Чтобы достичь максимальной гибкости, нужен язык общего назначения. Во фронтенде таким языком обычно является JS и другие языки, в него компилирующиеся. Поэтому очень велик соблазн либо собирать компоненты сразу в нём, либо засовывать в "шаблоны" вкрапления на JS. Разумеется ни о какой декларативности в этом случае уже говорить не приходится.
Чтобы добиться гибкости, но не потерять декларативность, нужно разбивать код компонента на 2 части:
- Декларативная, где происходит компоновка компонент друг с другом.
- Императивная, где описывается логика работы.
Именно поэтому надо отделять "шаблоны" от "скриптов", а не потому, что одно как бы бизнес-логика, а другое — её отображение.
Для примера возьмём язык view.tree, используемый в $mol:
$my_example $mol_view
sub /
<= Panel $my_bipanel
left <= input /
<= Editable $mol_check_box
checked?val <=> editable?val true
title @ \Editable
<= Input $mol_textarea
hint @ \Markdown content..
value?val <=> text?val \
enabled <= editable
right <= output /
<= Output_label $mol_paragraph
sub / <= output_label @ \Result
<= Output $mol_text
text <= text
Он мало того, что в несколько раз меньше эквивалентного JSX кода, так из него ещё и легко вычленять локализацию, собирать статистику использования, проверять переопределения стилей и много чего ещё. Можно даже построить конфигуратор позволяющий взять произвольное существующее приложение и, не написав ни строчки кода, собрать из него что-то новое. Ну а когда декларативных возможностей не хватает — всегда можно написать комплексную логику в отдельном скрипте, используя всю мощь языка общего назначения:
export class $my_example extends $.$my_example {
output() {
return this.text() ? super.output() : []
}
}
К сожалению, с подачи Фейсбука вместо чего-то такого мы имеем сейчас повсеместный императивный JSX и кучу костыльных проектов, пытающихся его программно анализировать. А в тех фреймворках, где есть отделение скриптов от шаблонов, вместо шаблонов мы видим императивный недо-DSL мимикрирующий под HTML, что приверженцы Реакта справедливо считают бессмысленным.
Что опять за наезды на JSX?
Раз уж мы уже наехали, то не будем останавливаться и проедемся до конца, по всем недостаткам дизайна JSX помимо недекларативности...
Push семантика
Вложенное поддерево вычисляется безусловно, даже если оно завёрнуто в компонент, который показывает своё содержимое лишь иногда. Решаться это могло бы через передачу замыкания вместо VDOM:
return (
<Dialog visible={ opened } >
{ ()=> <>Heavy content</> }
</Dialog>
)
Но, как всегда, есть "но":
- Заворачивать всё подряд в замыкания банально не удобно.
- Замыкания нужно мемоизировать через useCallback, чтобы избежать лишних рендеров.
- Без автоматического трекинга зависимостей это просто не будет работать.
- Изменение получения VDOM на замыкание меняет API компонента.
В результате реальный код становится куда более страшным:
const dialogContent = useCallback( ()=> (
<>Heavy content</>
) )
return userObserver( ()=> (
<Dialog visible={ opened } >
{ dialogContent }
</Dialog>
) )
Сравнение push и pull семантики — это отдельная большая тема. Поэтому вкратце обрисую преимущества pull: она позволяет просто и эффективно реализовать ленивые вычисления, рендеринг, загрузку и вообще экономить ресурсы. У push семантики же с этим всем серьёзные проблемы.
Неэффективность
JSX компилируется в крайне не удачный JS код, который из-за своей мегаморфности крайне сложно поддаётся оптимизации JIT-компилятором:
[url=https://perf.js.hyoo.ru/#!prefix=let%20res%0A%0Aconst%20A%20%3D%20props%20%3D%3E%20createElement%28%20%22input%22%2C%20props%2C%20null%20%29%0Aconst%20B%20%3D%20props%20%3D%3E%20createElement%28%20%22div%22%2C%20props%2C%20null%20%29%0Aconst%20C%20%3D%20props%20%3D%3E%20createElement%28%20%22a%22%2C%20props%2C%20null%20%29%0Aconst%20D%20%3D%20props%20%3D%3E%20createElement%28%20%22iframe%22%2C%20props%2C%20null%20%29%0Aconst%20E%20%3D%20props%20%3D%3E%20createElement%28%20%22object%22%2C%20props%2C%20null%20%29%0Aconst%20F%20%3D%20props%20%3D%3E%20createElement%28%20%22button%22%2C%20props%2C%20null%20%29%0Aconst%20G%20%3D%20props%20%3D%3E%20createElement%28%20%22img%22%2C%20props%2C%20null%20%29%0Aconst%20H%20%3D%20props%20%3D%3E%20createElement%28%20%22meta%22%2C%20props%2C%20null%20%29%0A/prefixes=%5B%22const%20createElement%20%3D%20%28%20tagName%2C%20props%2C%20children%20%29%3D%3E%5Cn%5Ct%28%7B%20tagName%2C%20props%2C%20children%20%7D%29%5Cn%22%2C%22const%20createElement%20%3D%20%28%20tagName%2C%20props%2C%20...%20children%20%29%3D%3E%5Cn%5Cttypeof%20tagName%20%3D%3D%3D%20'string'%5Cn%5Ct%5Ct%3F%20%7B%20tagName%2C%20props%2C%20children%20%7D%5Cn%5Ct%5Ct%3A%20tagName%28%20props%2C%20...%20children%20%29%5Cn%22%2C%22%22%5D/sources=%5B%22res%20%3D%20createElement%28%20%5C%22div%5C%22%2C%20%7B%7D%2C%20%5B%5Cn%5CtA%28%7B%20value%3A%20%5C%22A%5C%22%20%7D%29%2C%5Cn%5CtB%28%7B%20title%3A%20%5C%22B%5C%22%20%7D%29%2C%5Cn%5CtC%28%7B%20href%3A%20%5C%22C%5C%22%20%7D%29%2C%5Cn%5CtD%28%7B%20src%3A%20%5C%22D%5C%22%20%7D%29%2C%5Cn%5CtE%28%7B%20data%3A%20%5C%22E%5C%22%20%7D%29%2C%5Cn%5CtF%28%7B%20onclick%3A%20%5C%22F%5C%22%20%7D%29%2C%5Cn%5CtG%28%7B%20alt%3A%20%5C%22G%5C%22%20%7D%29%2C%5Cn%5CtH%28%7B%20charset%3A%20%5C%22H%5C%22%20%7D%29%2C%5Cn%5D%20%29%22%2C%22res%20%3D%20createElement%28%20%5C%22div%5C%22%2C%20null%2C%5Cn%5CtcreateElement%28%20A%2C%20%7B%20value%3A%20%5C%22A%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20B%2C%20%7B%20title%3A%20%5C%22B%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20C%2C%20%7B%20href%3A%20%5C%22C%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20D%2C%20%7B%20src%3A%20%5C%22D%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20E%2C%20%7B%20data%3A%20%5C%22E%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20F%2C%20%7B%20onclick%3A%20%5C%22F%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20G%2C%20%7B%20alt%3A%20%5C%22G%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20H%2C%20%7B%20charset%3A%20%5C%22H%5C%22%20%7D%20%29%2C%5Cn%29%22%5D]оригинал[/url]
Сверху — то, каким он мог бы быть быстрым при мономорфности. А снизу — суровая реальность в FireFox.
Слабые возможности связывания
JSX заточен под проталкивание значений. Но любые другие связывания — это боль. Хочешь передать замыкание — изволь завернуть его в useCallback и описать отдельным массивом всё, от чего оно зависит (и счастливой отладки, если что-то забудешь):
const setName = useCallBack( ( name: string )=> {
setInfo({ ... info, name })
}, [ info, setInfo ] )
return <Input value={ info.name } onChange={ setName }>
Самое забавное, что useCallback тут должен спасать от лишних рендеров, но так как замыкание зависит от info, то его приходится указывать в зависимостях, что приводит к обновлению замыкания при каждом изменении данных, даже если info.name фактически не поменялся. А следовательно рендер Input будет происходить при каждом изменении любого поля info.
Про двустороннее связывание я ранее уже упомянул, что оно делается через костыли с прокидыванием пары свойств. Это мало того, что многословно, так ещё и легко разъезжается, приводя ко крепким обнимашкам с дебаггером.
Неконсистентность
Из-за подражания HTML константные строки прокидываются одним синтаксисом, а все остальные типы и неконстантные строки — другим:
<input type="password" minLength={ 5 } className={ 'password ' + className } />
Дочерние компоненты могут быть переданы двумя совсем разными способами:
<Dialog>
<Hello />
<World />
</Dialog>
<Dialog
children={[
<Hello />,
<World />,
]}
/>
А уж сколько есть вариантов условного рендеринга — один хуже другого.
Всё это — следствие попытки усидеть сразу на двух стульях: HTML и JS.
Костыли для комментариев
Набирать и читать их просто неудобно:
<Dialog>
<Hello />
{/* World */}
</Dialog>
Волшебные атрибуты
JSX никак не форсирует простановку уникальных идентификаторов вложенным компонентам. А потребность получать ссылку на конкретный DOM элемент есть. Поэтому в вёрстке появляются волшебные атрибуты:
<Dialog>
<Hello ref={ setHelloRef } />
<World ref={ setWorldRef } />
</Dialog>
По той же причине, функция рендеринга не может отличить перемещённый элемент от нового, что приводит к лишнему рендерингу. Чтобы этого избежать вводится ещё один волшебный атрибут:
<Dialog>
<Message key="hello">Hello</Message>
<Message key="world">World</Message>
</Dialog>
Правда, при переносе в другого родителя, не спасает и он.
И беда даже не в том, что эти атрибуты вообще существуют, а в том, что синтаксически они неотличимы от любых других.
Много мусора в вёрстке
Мало нам закрывающих тегов из HTML. Давайте добавим ещё и лесенку из контекстов:
<ThemeContext.Provider value={theme} >
<UserContext.Provider value={signedInUser} >
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
<ThemeContext.Consumer>
{ theme => (
<UserContext.Consumer>
{ user => (
<ProfilePage user={user} theme={theme} />
) }
</UserContext.Consumer>
) }
</ThemeContext.Consumer>
Отсутствие ограничений
Отсутствие синтаксических ограничений мало того, что затрудняет программный анализ, так ещё и неизбежно приводит к ухудшению качества кода. Даже если код пишет не говнокодер, порой бывает, что некогда или лень делать правильно, а срезать угол ничего не мешает, и выглядит это вроде бы проще. Но разбитое окно тут, покосившееся там, и вот у нас уже не чётко структурированный код, а лютая тормозящая лапша, где всё связано со всем, лежит вперемешку, но концы с концами фиг сведёшь:
<div className="tag-list">
{tags.map((tag) => (
<button
key={tag}
className="tag-pill tag-default"
onClick={() =>
dispatch({
type: 'SET_TAB',
tab: { type: 'TAG', label: tag },
})
}
>
{tag}
</button>
))}
</div>
В цикле хреначим немемоизированное замыкание, код которого тут же по среди "вёрстки" и всё, что он делает, — меняет формат события. Не, ну а что, подумаешь ререндер всех элементов на каждый чих. Процессоры сейчас быстрые, данных мало, переход к определению работает везде..
А view.tree прям такой идеальный?
Нет, конечно, педаль в пол, давим и его..
Слабая интеграция с IDE
Microsoft добавила поддержку JSX прямо в компилятор TypeScript, что дало не только хороший тайпчек, но и интеграцию в тайпскриптовый Language Server. А это значит отличную интеграцию не только с их же VSCode, но и с другими IDE.
К сожалению, Microsoft не озаботилась простотой интеграции сторонних языков с TS. view.tree, конечно, компилируется в TS, что даёт тайпчек при сборке, но IDE этого всего не видит. Соответственно, не работают подсказки, рефакторинги и тп. Хорошо хоть подсветка синтаксиса есть.
Неявная типизация
Во имя простоты и наглядности в языке почти нет возможности задать тип явно. Типы выводятся из значений по умолчанию, что не всегда даёт ожидаемый результат.
Например, значение null имеет тип any:
/**
* Placeholder null
*/
Placeholder() {
return null as any
}
Как и аргументы методов:
/**
* name!id?next \Unknown
*/
@ $mol_mem_key
name(id: any, next?: any) {
if ( next !== undefined ) return next as never
return "Unknown"
}
Но это всё компромиссы конкретного языка, а не декларативного подхода в целом. Ведь можно же было сделать, например, так:
/**
* Placeholder null $mol_view
*/
Placeholder() {
return null as null | $mol_view
}
/**
* name!number?string \Unknown
*/
@ $mol_mem_key
name(id: number, next?: string) {
if ( next !== undefined ) return next
return "Unknown"
}
Что тут ещё сказать?
Фух, покатались на славу. Пришло время остановиться и перевести дух, поразмыслить над смыслом бытия, и двинуться дальше..
В выступлении "Tree — единый AST чтобы править всеми" можно познакомиться с форматом tree. В выступлении "Свой язык с поддержкой sourcemaps за полчаса" с его пайплайном. А в выступлении "$mol — лучшее средство от геморроя" можно найти краткое введение конкретно в язык view.tree.
Заглядывайте в чат "Разработка языков программирования" всё это обсудить. Или даже в чат "$mol: Разработка" если заинтересовал фреймворк $mol.
Ну и, конечно, предлагайте в комментариях свои идеи как упростить жизнь разработчика интерфейсов, не водружая инвалидные коляски поверх многоэтажных костылей.
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, ReactJS] Как начать работу с React Native, улучшить навигацию и перейти на новую библиотеку компонентов
- [JavaScript, LaTeX, Браузеры, Учебный процесс в IT] Парсинг Markdown и LaTeX в Grazie Chrome Plugin
- [Разработка веб-сайтов, JavaScript, Программирование, Управление разработкой] Круглый стол в Wrike: как перевести фронтенд на новый стек
- [Высокая производительность, Разработка веб-сайтов, Проектирование и рефакторинг] Highload там, где его не ждёшь. Приключение на 20 минут
- [ReactJS] Управление состоянием mapbox-gl в React
- [JavaScript, Интерфейсы, Математика] Конечные автоматы в реальной жизни: где мы их используем и почему
- [Разработка веб-сайтов, Программирование] Верите ли вы в бога надежности?
- [Разработка веб-сайтов, Анализ и проектирование систем, IT-инфраструктура, API, Софт] gRPC vs REST, что выбрать для нового сервера?
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS, Поисковая оптимизация] Next js. Куда, откуда и причем здесь google?
- [HTML, Accessibility] 3 техники с атрибутом aria-label, которые прокачают ваш HTML
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_html, #_angular, #_reactjs, #_templates, #_declarative, #_functional_programming, #_html, #_haml, #_jsx, #_view.tree, #_angular, #_reactjs, #_$mol, #_nocode, #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_html, #_angular, #_reactjs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:54
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Здравствуйте, меня зовут Дмитрий Карловский и я… люблю рвать шаблоны. А во фронтенде как раз крайне много заблуждений вокруг шаблонизации. Так что давайте порвём их на лоскуты снизу вверх и справа налево. Далее мы разберём что такое шаблоны. Их ключевые достоинства и фатальные недостатки. Зачем они нужны и почему не нужны. Сформируем представление о правильном решении и проедемся катком по популярным. Так что полная гамма чувств нам обеспечена. Прошу к столу.. А что такое шаблон? Казалось бы, тут всё очевидно: это способ генерации кода на целевом языке. Однако, всё не так просто. Чтобы понять ключевое свойство шаблона, давайте рассмотрим пару примеров.. Это шаблон: "Hello, ${name}!"
А это уже нет: "Hello" + name + "!"
Оба кода делают одно и то же, но почему одно является шаблоном, а другое — нет? Всё дело в том, какой язык является первичным в коде, а какой является опциональной добавкой к нему. Шаблон предполагает написание кода сразу на целевом языке, вкрапляя в него специальные управляющие конструкции, которые тем не менее синтаксически согласованы с целевым языком. Яркий пример синтаксически согласованных управляющих конструкций можно наблюдать в XSLT: <xsl:template name="page">
<acticle> <h1> <xsl:copy-of select="./head" /> </h1> <xsl:copy-of select="./body" /> </article> </xsl:template> А вот такой код, не смотря на использование шаблонов в 1 и 3 строке, в целом шаблоном всё же не является, так как чтобы понять, каков будет результат, нужно мысленно корректно исполнить JSX-код: const head = <h1>{ headContent }</h1>
const body = 'Hello, World' const article = <article>{ head }{ body }</article> Грубо говоря, если взять парсер целевого языка, то он справится и с парсингом шаблона, просто проигнорировав управляющие шаблонные конструкции, никак их не обрабатывая. И, соответственно, человек тоже может буквально видеть получающийся из шаблона результат, а не собирать его в уме. Как видно в последнем примере, код на JSX может быть шаблоном, а может им и не быть. И как правило шаблоном он всё же не является, не смотря на синтаксическое подражание HTML. А необходим ли HTML? Во фронтенде целевым языком для шаблонов как правило является HTML. А HTML является не более, чем сериализованным представлением DOM дерева. И в прошлом именно HTML был языком коммуникации между клиентом и сервером. Поэтому серверу нужно было генерировать именно его. Однако, в современном вебе клиент и сервер больше не обмениваются HTML, предпочитая JSON, ProtoBuf и другие более эффективные форматы. Более того, теперь клиент уже сам формирует DOM напрямую, через JS-API, минуя HTML представление. А это значит, что в качестве целевого языка описания DOM может быть использован не только HTML, но и иные форматы сериализации DOM. Например, HAML: !!!
%html{ :lang => "ru" } %head %title= title %meta{ 'http-equiv' => 'Content-Type', :content => 'text/html' }/ %body %h1= title %p= description Или xml.tree: ! DOCTYPE html
html @ lang \ru head title ? title meta @ content \text/html; charset=utf-8 @ http-equiv \Content-Type body h1 ? title p ? description Или даже JSON. Без примера, ибо слишком уж он развесистый получается. В этом свете использование HTML-шаблонизации является скорее данью традиции, чем реальной необходимостью: <!DOCTYPE html>
<html lang='ru'> <head> <title>{title}</title> <meta content='text/html; charset=utf-8' http-equiv='Content-Type' /> </head> <body> <h1>{title}</h1> <p>{description}</p> </body> </html> А достаточно ли HTML? Мощности HTML хватает лишь для описания DOM. Но современная разработка предполагает компонентную декомпозицию. А где декомпозиция — там и композиция. То есть нам необходим инструмент для создания экземпляров компонент, их настройки и соединения друг с другом реактивными связями разных направлений. Тут не то что HTML, а даже DOM уже катастрофически не хватает, что неизбежно порождает чудовищ. Например, вам нужно вставить несколько компонент и провязать их состояния друг с другом. Возьмём ангуляровский "шаблон": <bi-panel class="example">
<check-box class="editable" side="left" [(checked)]="editable" i18n > Editable </check-box> <text-area #input class="input" side="left" [(value)]="text" [enabled]="editable" placeholer="Markdown content.." i18n-placeholder="Showed when input is empty" /> <div *ngIf="text" class="output-label" side="right" i18n > Result </div> <mark-down *ngIf="text" class="output" side="right" text="{{text}}" /> </bi-panel> Весьма похоже на HTML, но только это не HTML, чтобы там ни говорили Angular-евангелисты. DOM (и как следствие HTML) поддерживают лишь задание строк в качестве атрибутов. А для компонент нужны не только строки, но и другие типы данных: числа, объекты и даже другие компоненты. И их надо не только хардкодить в шаблоне, но и брать из свойств, класть в свойства, а то и вообще обеспечивать двустороннее связывание. И тут начинаются кастомные расширения HTML. Каждый атрибут в примере выше имеет свою семантику, но синтаксически выглядят они все одинаково:
Все 4 компонента лежат вперемешку, не смотря на то, что часть из них относится к левому слоту, а часть к правому. То есть это мало того, что не HTML, так это ещё и вовсе не шаблон. Это — язык для компоновки компонент, мимикрирующий под HTML. Из-за этой мимикрии он преисполнен горой сомнительных решений, осложняющих изучение, разработку, чтение и поддержку как самого прикладного кода, так и инструментария, превращающего эти "шаблоны" во что-то, что может исполнить браузер, чтобы показать интерфейс. Но чем же HTML хорош? Ключевое достоинство HTML — его декларативность. Вопреки расхожему мнению, декларативные языки описывают на самом деле не "результат", а некоторую семантическую структуру. И эта структура может быть использована для программного анализа с получением множества разных "результатов" в зависимости от потребностей. Мы можем взять HTML и нарисовать на экране красивый плоский интерфейс. Можем в VR показать объёмный интерфейс, который можно потрогать. Можем реализовать голосовой интерфейс для не зрячих. Можем распечатать в виде книги. Можем собрать все заголовки для формирования оглавления и все термины для тезауруса. Можем собрать все ссылки и уведомить сайты, куда они ведут, о том, откуда на них ссылаются. Можем отправить уведомление всем упомянутым пользователям. И много чего ещё. Но всё это многообразие возможностей крайне затруднено, а то и попросту не возможно при написании императивного кода, который описывает конкретные действия по получению одного конкретного результата. Что бы там ни говорили адепты функционального программирования, но оно ни в коем разе не является декларативным. ФП — это просто императивное программирование на иммутабельных структурах, описывающее конкретные действия, по трансформации заданных входных параметров в заданные выходные. Всё, что можно сделать с чистой функцией — это её исполнить. А всё, что может дать нам анализ её кода — это "ну, там вызываются какие-то функции, вот их сигнатуры". Простой пример императивного функционального кода: приготовить_яичницу = ()=> последовательность(
()=> яйцо , яйцо => разбей( яйцо ) , разбитое_яйцо => уберать_скорлупу( разбитое_яйцо ), яйцо_без_скорлупы => пожарить( сковорода )( яйцо_без_скорлупы ), жаренное_яйцо => добавить_приправы( жаренное_яйцо ) ) А вот пример настоящего декларативного кода в модели RDF: яичница
включает жареное_яйцо приправы жареное_яйцо создаётся_посредством горячая_поверхность скворода является горячая_поверхность жареное_яйцо создаётся_из яйцо_без_скорлупы яйцо включает яйцо_без_скорлупы скорлупа Это логические триплеты. Благодаря нормализованному представлению их очень просто парсить и анализировать. Но вернёмся к нашим шаблонам. Возьмём популярный сейчас JSX, который мимикрирует не только под HTML, но и под JS, и даже под ФП, при этом ничем из упомянутого не являясь: const Example = ( props: {
className?: string text?: string onTextChanged?: ( next: string )=> void editable?: boolean onEditableToggle?: ( next: boolean )=> void } )=> { const [ stateText, setStateText ] = useState( props.text ?? '' ) const [ stateEditable, setStateEditable ] = useState( props.editable ?? true ) const [ inputElement, setInputElement ] = useState< HTMLTextAreaElement >( null ) const className = ( props.className ?? '' ) + ' example' const text = props.text ?? stateText const editable = props.editable ?? stateEditable const setText = useCallback( ( next: string )=> { setStateText( next ) props.onTextChanged?.( next ) }, [ props.onTextChanged ] ) const setEditable = useCallback( ( next: boolean )=> { setStateEditable( next ) props.onEditableToggle?.( next ) }, [ props.onEditableToggle ] ) return ( <BiPanel className={ className } left={ <> <CheckBox className="editable" checked={ editable } onToggle={ setEditable } > { l10n( 'Editable' ) } </CheckBox> <TextArea ref={ setInputElement } className="input" value={ text } onChange={ setText } enabled={ editable } placeholder={ l10n( 'Markdown content..' ) } /> </> } right={ text ? <> <div className="output-label" > { l10n( 'Result' ) } </div> <MarkDown className="output" text={ text } /> </> : <></> } /> ) } Закроем пока глаза на объёмность и сложность кода. Давайте подумаем, что мы можем получить из него без исполнения.. Можем ли мы при сборке вытащить все локализуемые тексты и заменить их на персистентные ключи?
Можем ли мы в визуальном конфигураторе понять, что свойства CheckBox.checked, TextArea.enabled и props.editable связаны друг с другом двусторонней связью?
Можем ли мы там же понять, что если не задать свойство editable, то текстария будет изначально редактируемой?
Можем ли мы при сборке проверить, что CSS-селектор .example .output .link действительно на что-то матчится?
Продолжать можно долго, но суть уже должна быть ясна: императивные языки содержат слишком мало информации о высокоуровневых абстракциях и слишком много низкоуровневого шума. Это капитально осложняет программный анализ. А возможна ли декларативность? Чем меньше язык позволяет вольностей, тем проще его программно анализировать. Но обратная сторона этого — снижение гибкости. Чтобы достичь максимальной гибкости, нужен язык общего назначения. Во фронтенде таким языком обычно является JS и другие языки, в него компилирующиеся. Поэтому очень велик соблазн либо собирать компоненты сразу в нём, либо засовывать в "шаблоны" вкрапления на JS. Разумеется ни о какой декларативности в этом случае уже говорить не приходится. Чтобы добиться гибкости, но не потерять декларативность, нужно разбивать код компонента на 2 части:
Именно поэтому надо отделять "шаблоны" от "скриптов", а не потому, что одно как бы бизнес-логика, а другое — её отображение. Для примера возьмём язык view.tree, используемый в $mol: $my_example $mol_view
sub / <= Panel $my_bipanel left <= input / <= Editable $mol_check_box checked?val <=> editable?val true title @ \Editable <= Input $mol_textarea hint @ \Markdown content.. value?val <=> text?val \ enabled <= editable right <= output / <= Output_label $mol_paragraph sub / <= output_label @ \Result <= Output $mol_text text <= text Он мало того, что в несколько раз меньше эквивалентного JSX кода, так из него ещё и легко вычленять локализацию, собирать статистику использования, проверять переопределения стилей и много чего ещё. Можно даже построить конфигуратор позволяющий взять произвольное существующее приложение и, не написав ни строчки кода, собрать из него что-то новое. Ну а когда декларативных возможностей не хватает — всегда можно написать комплексную логику в отдельном скрипте, используя всю мощь языка общего назначения: export class $my_example extends $.$my_example {
output() { return this.text() ? super.output() : [] } } К сожалению, с подачи Фейсбука вместо чего-то такого мы имеем сейчас повсеместный императивный JSX и кучу костыльных проектов, пытающихся его программно анализировать. А в тех фреймворках, где есть отделение скриптов от шаблонов, вместо шаблонов мы видим императивный недо-DSL мимикрирующий под HTML, что приверженцы Реакта справедливо считают бессмысленным. Что опять за наезды на JSX? Раз уж мы уже наехали, то не будем останавливаться и проедемся до конца, по всем недостаткам дизайна JSX помимо недекларативности... Push семантика Вложенное поддерево вычисляется безусловно, даже если оно завёрнуто в компонент, который показывает своё содержимое лишь иногда. Решаться это могло бы через передачу замыкания вместо VDOM: return (
<Dialog visible={ opened } > { ()=> <>Heavy content</> } </Dialog> ) Но, как всегда, есть "но":
В результате реальный код становится куда более страшным: const dialogContent = useCallback( ()=> (
<>Heavy content</> ) ) return userObserver( ()=> ( <Dialog visible={ opened } > { dialogContent } </Dialog> ) ) Сравнение push и pull семантики — это отдельная большая тема. Поэтому вкратце обрисую преимущества pull: она позволяет просто и эффективно реализовать ленивые вычисления, рендеринг, загрузку и вообще экономить ресурсы. У push семантики же с этим всем серьёзные проблемы. Неэффективность JSX компилируется в крайне не удачный JS код, который из-за своей мегаморфности крайне сложно поддаётся оптимизации JIT-компилятором: [url=https://perf.js.hyoo.ru/#!prefix=let%20res%0A%0Aconst%20A%20%3D%20props%20%3D%3E%20createElement%28%20%22input%22%2C%20props%2C%20null%20%29%0Aconst%20B%20%3D%20props%20%3D%3E%20createElement%28%20%22div%22%2C%20props%2C%20null%20%29%0Aconst%20C%20%3D%20props%20%3D%3E%20createElement%28%20%22a%22%2C%20props%2C%20null%20%29%0Aconst%20D%20%3D%20props%20%3D%3E%20createElement%28%20%22iframe%22%2C%20props%2C%20null%20%29%0Aconst%20E%20%3D%20props%20%3D%3E%20createElement%28%20%22object%22%2C%20props%2C%20null%20%29%0Aconst%20F%20%3D%20props%20%3D%3E%20createElement%28%20%22button%22%2C%20props%2C%20null%20%29%0Aconst%20G%20%3D%20props%20%3D%3E%20createElement%28%20%22img%22%2C%20props%2C%20null%20%29%0Aconst%20H%20%3D%20props%20%3D%3E%20createElement%28%20%22meta%22%2C%20props%2C%20null%20%29%0A/prefixes=%5B%22const%20createElement%20%3D%20%28%20tagName%2C%20props%2C%20children%20%29%3D%3E%5Cn%5Ct%28%7B%20tagName%2C%20props%2C%20children%20%7D%29%5Cn%22%2C%22const%20createElement%20%3D%20%28%20tagName%2C%20props%2C%20...%20children%20%29%3D%3E%5Cn%5Cttypeof%20tagName%20%3D%3D%3D%20'string'%5Cn%5Ct%5Ct%3F%20%7B%20tagName%2C%20props%2C%20children%20%7D%5Cn%5Ct%5Ct%3A%20tagName%28%20props%2C%20...%20children%20%29%5Cn%22%2C%22%22%5D/sources=%5B%22res%20%3D%20createElement%28%20%5C%22div%5C%22%2C%20%7B%7D%2C%20%5B%5Cn%5CtA%28%7B%20value%3A%20%5C%22A%5C%22%20%7D%29%2C%5Cn%5CtB%28%7B%20title%3A%20%5C%22B%5C%22%20%7D%29%2C%5Cn%5CtC%28%7B%20href%3A%20%5C%22C%5C%22%20%7D%29%2C%5Cn%5CtD%28%7B%20src%3A%20%5C%22D%5C%22%20%7D%29%2C%5Cn%5CtE%28%7B%20data%3A%20%5C%22E%5C%22%20%7D%29%2C%5Cn%5CtF%28%7B%20onclick%3A%20%5C%22F%5C%22%20%7D%29%2C%5Cn%5CtG%28%7B%20alt%3A%20%5C%22G%5C%22%20%7D%29%2C%5Cn%5CtH%28%7B%20charset%3A%20%5C%22H%5C%22%20%7D%29%2C%5Cn%5D%20%29%22%2C%22res%20%3D%20createElement%28%20%5C%22div%5C%22%2C%20null%2C%5Cn%5CtcreateElement%28%20A%2C%20%7B%20value%3A%20%5C%22A%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20B%2C%20%7B%20title%3A%20%5C%22B%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20C%2C%20%7B%20href%3A%20%5C%22C%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20D%2C%20%7B%20src%3A%20%5C%22D%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20E%2C%20%7B%20data%3A%20%5C%22E%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20F%2C%20%7B%20onclick%3A%20%5C%22F%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20G%2C%20%7B%20alt%3A%20%5C%22G%5C%22%20%7D%20%29%2C%5Cn%5CtcreateElement%28%20H%2C%20%7B%20charset%3A%20%5C%22H%5C%22%20%7D%20%29%2C%5Cn%29%22%5D]оригинал[/url] Сверху — то, каким он мог бы быть быстрым при мономорфности. А снизу — суровая реальность в FireFox. Слабые возможности связывания JSX заточен под проталкивание значений. Но любые другие связывания — это боль. Хочешь передать замыкание — изволь завернуть его в useCallback и описать отдельным массивом всё, от чего оно зависит (и счастливой отладки, если что-то забудешь): const setName = useCallBack( ( name: string )=> {
setInfo({ ... info, name }) }, [ info, setInfo ] ) return <Input value={ info.name } onChange={ setName }> Самое забавное, что useCallback тут должен спасать от лишних рендеров, но так как замыкание зависит от info, то его приходится указывать в зависимостях, что приводит к обновлению замыкания при каждом изменении данных, даже если info.name фактически не поменялся. А следовательно рендер Input будет происходить при каждом изменении любого поля info. Про двустороннее связывание я ранее уже упомянул, что оно делается через костыли с прокидыванием пары свойств. Это мало того, что многословно, так ещё и легко разъезжается, приводя ко крепким обнимашкам с дебаггером. Неконсистентность Из-за подражания HTML константные строки прокидываются одним синтаксисом, а все остальные типы и неконстантные строки — другим: <input type="password" minLength={ 5 } className={ 'password ' + className } />
Дочерние компоненты могут быть переданы двумя совсем разными способами: <Dialog>
<Hello /> <World /> </Dialog> <Dialog
children={[ <Hello />, <World />, ]} /> А уж сколько есть вариантов условного рендеринга — один хуже другого. Всё это — следствие попытки усидеть сразу на двух стульях: HTML и JS. Костыли для комментариев Набирать и читать их просто неудобно: <Dialog>
<Hello /> {/* World */} </Dialog> Волшебные атрибуты JSX никак не форсирует простановку уникальных идентификаторов вложенным компонентам. А потребность получать ссылку на конкретный DOM элемент есть. Поэтому в вёрстке появляются волшебные атрибуты: <Dialog>
<Hello ref={ setHelloRef } /> <World ref={ setWorldRef } /> </Dialog> По той же причине, функция рендеринга не может отличить перемещённый элемент от нового, что приводит к лишнему рендерингу. Чтобы этого избежать вводится ещё один волшебный атрибут: <Dialog>
<Message key="hello">Hello</Message> <Message key="world">World</Message> </Dialog> Правда, при переносе в другого родителя, не спасает и он. И беда даже не в том, что эти атрибуты вообще существуют, а в том, что синтаксически они неотличимы от любых других. Много мусора в вёрстке Мало нам закрывающих тегов из HTML. Давайте добавим ещё и лесенку из контекстов: <ThemeContext.Provider value={theme} >
<UserContext.Provider value={signedInUser} > <Layout /> </UserContext.Provider> </ThemeContext.Provider> <ThemeContext.Consumer>
{ theme => ( <UserContext.Consumer> { user => ( <ProfilePage user={user} theme={theme} /> ) } </UserContext.Consumer> ) } </ThemeContext.Consumer> Отсутствие ограничений Отсутствие синтаксических ограничений мало того, что затрудняет программный анализ, так ещё и неизбежно приводит к ухудшению качества кода. Даже если код пишет не говнокодер, порой бывает, что некогда или лень делать правильно, а срезать угол ничего не мешает, и выглядит это вроде бы проще. Но разбитое окно тут, покосившееся там, и вот у нас уже не чётко структурированный код, а лютая тормозящая лапша, где всё связано со всем, лежит вперемешку, но концы с концами фиг сведёшь: <div className="tag-list">
{tags.map((tag) => ( <button key={tag} className="tag-pill tag-default" onClick={() => dispatch({ type: 'SET_TAB', tab: { type: 'TAG', label: tag }, }) } > {tag} </button> ))} </div> В цикле хреначим немемоизированное замыкание, код которого тут же по среди "вёрстки" и всё, что он делает, — меняет формат события. Не, ну а что, подумаешь ререндер всех элементов на каждый чих. Процессоры сейчас быстрые, данных мало, переход к определению работает везде.. А view.tree прям такой идеальный? Нет, конечно, педаль в пол, давим и его.. Слабая интеграция с IDE Microsoft добавила поддержку JSX прямо в компилятор TypeScript, что дало не только хороший тайпчек, но и интеграцию в тайпскриптовый Language Server. А это значит отличную интеграцию не только с их же VSCode, но и с другими IDE. К сожалению, Microsoft не озаботилась простотой интеграции сторонних языков с TS. view.tree, конечно, компилируется в TS, что даёт тайпчек при сборке, но IDE этого всего не видит. Соответственно, не работают подсказки, рефакторинги и тп. Хорошо хоть подсветка синтаксиса есть. Неявная типизация Во имя простоты и наглядности в языке почти нет возможности задать тип явно. Типы выводятся из значений по умолчанию, что не всегда даёт ожидаемый результат. Например, значение null имеет тип any: /**
* Placeholder null */ Placeholder() { return null as any } Как и аргументы методов: /**
* name!id?next \Unknown */ @ $mol_mem_key name(id: any, next?: any) { if ( next !== undefined ) return next as never return "Unknown" } Но это всё компромиссы конкретного языка, а не декларативного подхода в целом. Ведь можно же было сделать, например, так: /**
* Placeholder null $mol_view */ Placeholder() { return null as null | $mol_view } /** * name!number?string \Unknown */ @ $mol_mem_key name(id: number, next?: string) { if ( next !== undefined ) return next return "Unknown" } Что тут ещё сказать? Фух, покатались на славу. Пришло время остановиться и перевести дух, поразмыслить над смыслом бытия, и двинуться дальше.. В выступлении "Tree — единый AST чтобы править всеми" можно познакомиться с форматом tree. В выступлении "Свой язык с поддержкой sourcemaps за полчаса" с его пайплайном. А в выступлении "$mol — лучшее средство от геморроя" можно найти краткое введение конкретно в язык view.tree. Заглядывайте в чат "Разработка языков программирования" всё это обсудить. Или даже в чат "$mol: Разработка" если заинтересовал фреймворк $mol. Ну и, конечно, предлагайте в комментариях свои идеи как упростить жизнь разработчика интерфейсов, не водружая инвалидные коляски поверх многоэтажных костылей. =========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ), #_javascript, #_html, #_angular, #_reactjs |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:54
Часовой пояс: UTC + 5