Skip to content

Latest commit

 

History

History
150 lines (118 loc) · 15.8 KB

MIGRATION.md

File metadata and controls

150 lines (118 loc) · 15.8 KB

Гид по миграции с Endorphin 1 на Endorphin 2

Шаблоны

Шаблоны, в основном, остались прежними по структуре, но поменялись детали:

  • Все контрольные инструкции используют XML namespace end. Например, теперь надо писать <end:if> вместо <t-if>.
  • Сами шаблоны теперь используют строгий XML. А это значит, что все тэги нужно обязательно закрывать. А все пустые тэги можно писать в сокращённой форме, типа <div />. Однако можно писать атрибуты без кавычек и использовать < в тексте без экранирования.
  • События привязываются через namespace on: <div on:click={foo()}>.
  • Использование выражений стало более строгим: теперь везде, где используются выражения, надо использовать {...}. Например, если раньше условия в некоторых конструкциях можно было писать в виде строковых литералов (<t-if test="foo">), теперь их строго нужно писать внутри {...}: <end:if test={foo}. В большинстве случаев компилятор ругнётся, если где-то забыли выражение.
  • Изменилась схема обращения к свойствам, состоянию и локальным переменным:

Было

  • @prop — обращение к атрибуту prop компонента
  • state('foo') — обращение к параметру foo локального состояния
  • $item — обращение к локальной переменной шаблона item

Стало

  • prop — обращение к атрибуту prop компонента

  • #foo — обращение к параметру foo локального состояния

  • @item — обращение к локальной переменной шаблона item

  • $data — обращение к свойству data стора компонента (пока не реализовано)

  • Изменилась логика работы циклов: теперь контекст не переключается на итерируемый элемент, а остаётся глобальным для компонента. Это значит, что теперь для обращения к свойству родительского компонента внутри цикла не нужно создавать локальную переменную со значением атрибута. В самом цикле итерируемый элемент присваивается локальной переменной $value. Также создаются переменные $index (позиция интегрируемого элемента в списке, аналог position()) и $key (ключ элемента в коллекции, аналог name()). /TODO: придумать синтаксис для указания названий этих переменных./

  • Выражения стали более похожими на JavaScript: теперь [...] — это обращение к свойству объекта, как в JS, предикатов больше нет, как и выборок по .*. Вместо них используются /фильтры/. Фильтр — это стрелочная функция, которая передаётся в качестве ключа для получения значения: arr[value => value > 10]. На чистом JS эта запись означает «дай мне значение из arr по ключу, который равен строгому представлению выражения, то есть "value => value > 10", что не имеет практического смысла в данном случае. Поэтому компилятор такое выражение превращает в аналог arr.filter(value => value > 10) с той разницей, что arr может быть любой итерируемой коллекцией, не только массивом. Фильтр всегда возвращает массив отфильтрованных значений, преобразования, когда массив из одного значения превращался в само значение больше нет, чтобы избежать путаницы.

  • Изменился синтаксис добавления атрибутов и объявления переменных. Если раньше для указания атрибута или переменной нужно было использовать контрольную инструкцию с парой атрибутов name="..." и value="...", то теперь нужно писать name=value. То есть в одной инструкции можно передать несколько атрибутов/переменный: <end:attribute foo="bar" baz={a + b} />

  • Названия атрибутов также могу быть выражениями: <div {a ? 'foo' : 'bar'}="10" />.

  • Любому элементу можно добавить атрибут end:if={...}, это будет то же самое, что обрамить элемент в <end:if test={...}>: <div end:if={enabled}>foo</div> при компиляции превратиться в <end:if test={enabled}><div>foo</div></end:if>

  • Выражения внутри текста теперь пишутся через ординарные {...} вместо {{...}}. В {{...}} теперь нужно передавать текст, который должен рендерится как HTML.

  • Поддержка refs: теперь любому элементу можно указать ref="refname" и этот элемент будет доступен в компоненте как component.refs.refname.

  • В дополнение к <end:add-class> можно добавлять классы элементу через атрибут вида class:name={cond}: <div class:enabled class:first={$index === 0}> более короткая версия кода <div><end:add-class>enabled</end:add-class><end:add-class if="$index === 0">first</end:add-class></div>.

  • Булевые атрибуты теперь можно передавать как в HTML, просто указанием названия атрибута: <input type="radio" checked />. Если используется выражение, то булевой атрибут выведется только в том случае, если его значение будет истинным и не выведется совсем, если ложным.

  • Для простых случаев для обработки событий не обязательно создавать отдельную функцию в компоненте, которая будет, например, запускать событие или менять стэйт, можно воспользоваться встроенными функциями: <div on:click={emit('clicked')} on:mouseenter={ setState({hovered: true}) } on:mouseleave={ setState({hovered: false}) }>.

  • При импорте дочерних компонентов теперь обязательно нужно указывать атрибут as="tag-name" с названием тэга, для которого этот импорт предназначен: <link rel="import" href="inner-component.html" as="inner-component" /> В противном случае тэг <inner-component> будет создан как обычный DOM-элемент, а не компонент. С другой стороны, это позволит проще экспериментировать, заменяя реализации компонента на другую в рамках эксперимента.

Поддержка partials

Шаблоны теперь поддерживают partials: переиспользуемые и переопределяемые фрагменты кода:

<template>
	<ul>
		<end:for-each select={items}>
			<partial:button item={$item} enabled={$index !== 1} />
		</end:for-each>
	</ul>
</template>

<template partial:button item enabled={true} pos={0}>
	<li class:enabled={$enabled}>{ $item }</li>
</template>

Выведет

<ul>
	<li class="enabled">item 1</li>
	<li>item 2</li>
	<li class="enabled">item 3</li>
</ul>

Partial объявляется как элемент <template> верхнего уровня с указанием partial:name, где name — это название шаблона. В качестве атрибутов указываются аргументы шаблона и их значения по умолчанию. Чтобы вызвать его , нужно в месте использования написать <partial:name ...args />. Также partials можно переопределять. Например, если внутри компонента <my-component> используется partial с названием my-item, то его можно переопределить, передав в качестве свойства имя локального partial: <my-component partial:my-item="my-local-item" />. ВАЖНО: тело partial исполняется в контексте компонента, в котором он отрисовывается, а заучит имеет доступ к локальным переменным, стэйту и свойствам компонента, в котором отрисовывается, а не в котором определен.

Компонент

Описание компонента теперь представляет из себя обычный ES-модуль, который экспортирует свои данные и обработчики (публичный контракт). Также описание компонента подразумевает функциональный стиль : все экспортируемые методы являются общими для всех экземпляров компонента. Каждому методу в качестве первого аргумента приходит экземпляр компонента, для которого он должен сработать.

Пример описания компонента:

// Вызывается при создании компонента, возвращает props со значениями по умолчанию
export function props() {
	return { a: 1, b: 2 };
}

// Вызывается при создании компонента, возвращает state со значениями по умолчанию
export function state() {
	return { foo: 'bar' };
}

// Список методов, которые нужно добавить DOM-элементу компонента.
// Это будет его публичный контракт: если получить DOM-элемент компонента,
// за эти методы его можно дёргать
export const methods = {
	enable() {
		this.setState({ enabled: true });
	}
}

// Инициализация компонента
export function init(component) {
	console.log('inited');
}

// Вместо компонента удобно использовать деструктуризауию,
// чтобы удобнее получать и обновлять данные
export function onClick({ props, setState }) {
	setState({ enabled: props.enabled });
}

Список всех известных свойств и методов описания компонента доступен в интерфейсе ComponentDefinition.

Также концептуально поменялся публичный контракт передаваемых свойств компонента. Теперь это не атрибуты, у которых была поддержка типов данных, а свойство props. Атрибуты у компонента теперь используются в качестве наглядного представления соответствующих данных в props. Чтобы получить свойства и стэйт компонента, нужно использовать .props и .state соответственно. А для обновления вызывать методы .setProps({ prop1: 1, prop2: 2 }) и .setState({ state1: 1, state2: 2 }). Оба метода отвязано от контекста, то есть могут использоваться как самостоятельные функции после деструктуризации.

Также из-за того, что атрибуты не являются источником данных, отсутствует метод attributeChangedCallback(): вместо него нужно использовать один из will* методов, в который передаётся список изменившихся пропсов и стэйтов, которые привели к перерисовке компонента.

Методы жизненного цикла также были расширены. Теперь в компоненте чётко разделяются стадии монтирования: willMount()/didMount() (первичная отрисовка компонента) и willUpdate()/didUpdate() (все последующие обновления). Если нужно реагировать на все изменения, то нужно по-прежнему использовать willRender()/didRender(). Также появились методы willUnmount() (компонент буден удалён, но всё ещё находится в DOM и работает) и didUnmount() (компонент полностью удалён).

Вспомогательные методы вроде диспетчеризации событий также были вынесены в отдельные функции и подключаются по запросу:

import { on, emit } from 'endorphin';

export function init(component) {
	on(component, 'play', () => emit(component, 'play-init'));
}

Стили и скрипты в шаблонах

В шаблона стили и скрипты можно подключать как внешними ресурсами:

<template>
	<p>Hello world</p>
</template>
<script src="my-component.js" />
<link rel="stylesheet" href="my-component.scss" />

...так и писать инлайн:

<template>
	<p>Hello world</p>
</template>
<script>
export function init() {
	console.log('inited');
}
</script>
<style type="scss">
p {
	font-size: 20px;
}
</style>

В случае, если скрипты и стили отличаются от JS и CSS, в тэгах <script> и <style> можно указать атрибут с MIME-типом кода, который там используется: этот тип будет учтён при компиляции.