Буквально вчера вышла 2-я версия молодого, но весьма многообещающего фреймворка SvelteJS. Версия мажорная, а значит содержит не только новые фичи и исправленные баги, но и соответствующие «breaking changes». Что новенького предлагает разработчикам новая версия и почему Svelte стал еще лучше, читайте под катом.
Если вдруг, по какой-то неведомой причине, вы не знаете что такое Svelte и почему это не «yet another javascript framework». Предлагаю сперва наверстать упущенное, чтобы лучше понимать о чем речь.
Новый синтаксис шаблонов
Самое очевидное и глобальное изменение в новой версии — кардинальная смена синтаксиса шаблонов. Рич наконец-то решил избавиться от «усо»-подобного синтаксиса в пользу более лаконичного варианта:
Было
{{#if foo}}
{{bar}}
{{else}}
{{baz}}
{{/if}}
Стало
{#if foo}
{bar}
{:else}
{baz}
{/if}
Очевидно что синтаксис стал визуально проще и чище. Изменения коснулись всех конструкций в шаблонах, в том числе специальных элементов Svelte:
Было
<:Component {foo ? Red : Blue} name="thing" />
{{#if foo}}
<:Self />
{{/if}}
<:Window on:keydown="handleKey(event)" />
<:Head>
<title>{{post.title}} • My blog</title>
</:Head>
Стало
<svelte:component this="{foo ? Red : Blue}" name="thing"/>
{#if foo}
<svelte:self/>
{/if}
<svelte:window on:keydown="handleKey(event)" />
<svelte:head>
<title>{post.title} • My blog</title>
</svelte:head>
Предыдущий синтаксис специальных элементов был уж слишком необычен и большинство редакторов кода не справлялись с его подсветкой. Новый синтаксис напоминает синтаксис namespace из XML и значительно лучше воспринимается редакторами.
Надо отдать должное Ричу и отдельно отметить, что все изменения активно обсуждались с сообществом, а некоторые мои предложения вошли в финальный вариант синтаксиса и, вроде как, даже способствовали упрощению парсера.
В общем изменений в синтаксисе много и это могло бы стать проблемой для миграции на новую версию, если бы не утилита svelte-upgrade, специально созданная для автоматического апгрейда Svelte-компонентов. Полный список изменений, можно посмотреть там же.
Годно, Рич! Прощайте «усы»!
ES6 only
Так как Svelte — это прежде всего компилятор, стоит сначала отметить, что итоговый код предыдущей версии компилировался в ES5. Поэтому для поддержки IE11 и других «прогрессивных» версий браузеров, не было нужды связываться с транспиллерами вроде Babel или Bublé.
Но на дворе 2018-й и чтобы компилятор мог продуцировать более оптимальный и еще более компактный код, было принято решение отказаться от поддержки ES5. Иными словами, теперь Svelte компилирует компоненты в ES6 и нам придется использовать транспиллер, если необходима поддержка старых версий.
Сам я полностью поддерживаю такой подход. Тем более что подключить Babel к Webpack или Rollup, уверен, ни для кого уже не составит труда. Особенно если учесть, что использовать Svelte без оных все равно не получится. ;-)
Actions
До сих пор не понимаю почему эта фича называется actions, но для себя решил, что носителям языка виднее. Хотя лично для меня — это не очевидное название.
В любом случае, фича полезная. Фактически это некий хук, который срабатывает, когда элемент рендерится в DOM. Для этого введена новая директива use:
<img src="placeholder.jpg" use:lazyload="{ src: 'giant-photo.jpg' }">
И соответствующая секция в поведении:
export default {
actions: {
lazyload(node, data) {
// do something
return {
update(data) {},
destroy() {}
}
}
}
};
Экшн — это функция, которая принимает первым параметром элемент, к которому применена директива, и данные, которые были переданы в нее. Функция должна вернуть объект с обязательным методом destroy(), который будет вызван в тот момент, когда элемент будет удален из DOM. Также объект может содержать не обязательный методом update(), который будет вызываться каждый раз, когда связанные с экшеном данные были изменены.
В общем, если есть необходимость производить манипуляции с DOM напрямую, мимо реактивности Svelte, экшены позволяют делать это удобно и дают механизм синхронизации этих изменений.
Новые хуки жизненного цикла
В предыдущей версии были лишь 2 хука: oncreate() и ondestroy(). Теперь мы имеем также 2 дополнительных хука, отвечающих за работу с состоянием:
export default {
onstate({ changed, current, previous }) {
// вызывается до oncreate(), и каждый раз, когда состояние изменилось
},
onupdate({ changed, current, previous }) {
// вызывается после oncreate(), и каждый раз, когда DOM был обновлен после изменения состояния
}
};
Как видите, каждый хук принимает объект с 3-мя свойствами:
- changed — включает в себя ключи, которые были изменены в стейте. Используется для проверки
- current — измененный стейт
- previous — предыдущий стейт
Иcпользовать можно так:
export default {
onstate({ changed: { foo }, current, previous }) {
if (foo) {
console.log('foo has changed from %s to %s', previous.foo, current.foo);
}
}
};
Или даже так:
component.on('state', ({ changed, current, previous }) => {...});
В связи с этим важным изменением метод observe() был вынесен из ядра в пакет дополнений svelte-extras. Поэтому если нравится предыдущий синтаксис, можно просто подключить соответствующий метод из этого пакета:
import { observe } from 'svelte-extras';
export default {
methods: { observe },
oncreate() {
this.observe('foo', (current, previous) => {...});
}
};
Если вспомнить что Рич, как создатель Rollup, является фанатом tree-shaking'а, такой подход сразу становится очевидным.
Spread attributes
Да, знаю, это подсмотрели у JSX, но сути это не меняет. Многие проголосовали ЗА и теперь Svelte умеет также:
<Child {...childProps} />
Другие изменения
Немаловажные изменения произошли и в некоторых существующих api фреймворка. Вот основные из них:
Метод get() больше не принимает параметры и всегда возвращает весь стейт компонента:
Было
const foo = this.get('foo');
const bar = this.get('bar');
Стало
const { foo, bar } = this.get();
Это прикольно и мы можем использовать деструктурирующее присваивание для определения необходимых свойств. К тому же, теперь данный метод стал больше похож на своего антагониста, метод set(), который еще в предыдущей версии принимал исключительно объект:
this.set({ foo: 1 });
const { foo } = this.get();
Вообще у меня создается впечатление, что Svelte все больше склоняется к использованию RORO в своих интерфейсах. А полный переход на ES6 лишь способствует этому.
Это же наблюдение подтверждает новый синтаксис вычисляемых свойств:
Было
export default {
computed: {
d: (a, b, c) => a = b + c
}
};
Стало
export default {
computed: {
d: ({ a, b, c }) => a = b + c
}
};
На первый взгляд странное и не слишком полезное изменение (разве что RORO), но на самом деле следующим шагом будет возможность создания вычисляемого свойства зависящего от всего стейта компонента. Например, для его фильтрации или иных манипуляций, а также передачи в дочерние компоненты с помощью spread-аттрибута, примерно таким образом (пока это не работает):
<Child {...props}/>
<script>
import Child from './Child.html';
export default {
components: { Child },
computed: {
props: state => {
const { unwanted, alsoUnwanted, ...props } = state;
return props;
}
}
};
</script>
Думаю многие понимают насколько это круто. Надеюсь Рич запилит эту фичу в ближайшее время.
Обработчики кастомных ивентов теперь должны возвращать destroy() вместо teardown() для консистентности:
Было
export function eventHandler(node, callback) {
//...
return {
teardown() {}
}
}
Стало
export function eventHandler(node, callback) {
//...
return {
destroy() {}
}
}
Svelte больше не приводит значения аттрибутов компонентов к типу
Теперь нужно явно указывать тип отличный от строки с помощью выражения. Более всего это касается чисел:
Было
<Counter start="1"/>
Стало
<Counter start="1"/> <!-- строка -->
<Counter start="{1}"/> <!-- число -->
Думаю смысл понятен. Мудрое решение.
В шаблонах методы стора теперь можно вызывать через префикс $.
Было
<button on:click="store.set({ clicked: true })">click me</button>
Стало
<button on:click="$set({ clicked: true })">click me</button>
В предыдущей версии через префикс $ были доступны только данные из стора.
Тудушечка
Для наглядности накатал свою собственную «тудушечку». В ней я постарался отразить максимум новых возможностей Svelte, которые можно применить к данной задаче, опять же для наглядности.
Тудушечка умеет CRUD над задачами, эмуляцию асинхронного взаимодействия с персистентным стейтом (хранилищем, бекендом и т.п.) и querying'ом по одному параметру — типу todo-листа (work, family, hobby), а также легкими анимашками. Работает примитивно, пишется быстро. Все как я люблю ))))
→ Пощупать
Вот и все, всем спасибо! Хорошей пятницы и выходных!
Автор: PaulMaly