Попробовав Svelte в личных проектах, мне захотелось двигаться дальше, и взять фреймворк в проект побольше. Для этого написал библиотеку компонентов svelte-atoms. За основу я взял UI кит на React, который используем на работе.
Каким приемам Svelte я научился, читайте под катом.
Статья подразумевает, что вы уже знакомы с основными концепциями Svelte. Если нет, то рекомендую сначала ознакомиться с моими предыдущими статьями:
Полная жизнь на Svelte
Разрабатываем игру на Svelte 3
Библиотека находится в фазе активной доработки. Еще не хватает некоторых компонентов, не проработаны вопросы доступности, и не все баги исправлены. Сейчас делаю проект на этом ките, чтобы в боевом режиме понять, что нужно дорабатывать в первую очередь.
Итак, чему я научился, написав библиотеку компонентов на Svelte.
1. Использование свойства class
Если вы хотите добавить свойство class к параметрам своего компонента, то получите ошибку, поскольку слово является зарезервированным самим javascript.
<script>
export let class = '';
// !!! ошибка !!!
</script>
Решить эту проблему можно, используя внутреннее api Svelte. Переменная $$props содержит все свойства, которые переданы в компонент. В компоненте-родителе можно передать свойство class
<Component class="someClass" />
А в самом компоненте установить класс из переменной $$props.
<div class={$$props.class} />
2. Стилизация дочерних компонентов
В Svelte используется изоляция стилей. Если вы добавили класс в компоненте, то в итоге к нему добавится хеш и он будет уникальным для вашего компонента и никуда не "протечет". Но что, если нужно стилизовать дочерний компонент? Логично передать ему какой-нибудь класс и в родителе прописать стили. Решается данная задача довольно просто, с помощью директивы :global()
<script>
import Component from "./Component.svelte";
</script>
<div class="parent">
<Component class="childClass" />
</div>
<style>
.parent :global(.childClass) {
color: red;
}
</style>
При таком подходе вы все равно получите изолированный стиль, но который применяется во всей иерархии дочерних компонентов
3. Обработчики всех событий
Svelte компонент должен поддерживать событие, чтобы извне можно было назначить на него обработчик. Прописывать все варианты событий утомительно. Вместо этого можно написать универсальный обработчик, который будет искать обработчики событий, которые переданы в компонент, и добавлять их в наш компонент. Идею подсмотрел у AlexxNB в его библиотеке svelte-chota.
<script>
import { current_component, bubble, listen } from "svelte/internal";
function getEventsAction(component) {
return node => {
const events = Object.keys(component.$$.callbacks);
const listeners = [];
events.forEach(event =>
listeners.push(listen(node, event, e => bubble(component, e)))
);
return {
destroy: () => {
listeners.forEach(listener => listener());
}
};
};
}
const events = getEventsAction(current_component);
export let value;
</script>
<input use:events {value} />
Этот способ не совсем легальный, поскольку использует внутреннее API Svelte, которое может измениться. Надеюсь, в будущем добавится поддержка директивы on: * Issue на github
4. Обнаружение слотов
Если вам нужно узнать, передано ли содержимое слота, то могу предложить вам два варианта.
Способ первый, легальный
У слотов есть фолбек, который отображается, если содержимое не передано. Сделав биндинг фолбэка в переменную, мы сможем обнаружить наш слот.
<script>
let footerRef = null;
$: isFooterExists = Boolean(footerRef);
</script>
<slot name="footer">
<div bind:this={footerRef} />
</slot>
{isFooterExists ? 'Футер есть' : 'Футера нет'}
Способ второй, полулегальный
Можно воспользоваться внутренним api Svelte
const isFooterExists = Boolean($$props.$$slots && $$props.$$slots.footer);
5. Порталы
В Svelte нет приема использования порталов, как в React, но его очень просто сделать. Для этого можно воспользоваться DOM api.
<script>
import { onMount } from "svelte";
let ref;
onMount(() => {
document.body.appendChild(ref);
return () => {
document.body.removeChild(ref);
};
});
</script>
<div>
<div bind:this={ref}>content</div>
</div>
На монтирование компонента мы переносим его в body, а при удалении убираем и наш портал.
Важное замечание: заверните ваш портал в div, иначе Svelte может некорректно убрать компоненты при размонтировании.
6. Анимации
В Svelte большой набор готовых анимаций, которые могут пригодиться вам в работе. Очень легко анимировать появление и исчезание компонентов. Но анимация одного блока может тормозить удаление всей страницы. Svelte будет ждать завершения анимации, прежде чем удалить компонент из дерева. Чтобы избежать этого, используйте директиву local.
Пример в туториале
7. Именованные слоты и компоненты
К сожалению, передать в именованный слот сразу компонент нельзя. Issue на github
<Component>
<Child slot='footer'/>
</Component>
<!-- так не работает -->
Чтобы передать компонент в именованный слот заверните его в div или любой другой html тег.
<Component>
<div slot='footer'>
<Child />
</div>
</Component>
<!-- Работает! -->
8. Получение списка свойств компонента
Если вам нужно получить список свойств, которые экспортированы из компонента, то можно воспользоваться следующей конструкцией:
<script>
import Component from './Component.svelte';
const [_, ...props] = Object.getOwnPropertyNames(Component.prototype);
</script>
{JSON.stringify(props)}
Идею подсмотрел у PaulMaly.
9. Двухсторонний биндинг
После разработки на React, концепция двухсторонней привязки кажется пугающей, но это только на первый взгляд. В итоге ваше приложение будет выглядеть лаконичней и позволит избавиться от кучи обработчиков. Вы можете использовать в качестве хранилища как обычную переменную, так и store.
<script>
import Input from "./Input.svelte";
import { writable } from "svelte/store";
let letValue = "test let";
const storeValue = writable("test store");
</script>
<Input bind:value={letValue} />
<Input bind:value={$storeValue} />
Пример в REPL
10. Редактируемый контент
Если вам нужно сделать контент элемента редактируемым, то можно воспользоваться директивой contenteditable и сделать биндинг значения в переменную.
<script>
let value = "Edit me";
</script>
<div contenteditable="true" bind:textContent={value}>{value}</div>
<div>Value: {value}</div>
Пример в REPL
Размер компонентов
Ну и куда же без React. Держите сравнение количество строк кода компонентов на Svelte и React, которые реализованы примерно одинаково. Учитываются код и стили.
Демо библиотеки
Исходный код
Если вы нашли баг, или хотите что-то предложить, создавайте issue
P.S.
22 февраля 2020 года пройдет Svelte Russian Meetup #1 в Москве. Подробности и официальный анонс в телеграм группе русского сообщества Svelte: @sveltejs
Приглашаю всех принять участие!
Автор: Alexander Zinchenko