Pull to refresh

Чему я научился, написав библиотеку компонентов на Svelte

Reading time5 min
Views13K


Попробовав 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} />

Пример в REPL


2. Стилизация дочерних компонентов


В Svelte используется изоляция стилей. Если вы добавили класс в компоненте, то в итоге к нему добавится хеш и он будет уникальным для вашего компонента и никуда не "протечет". Но что, если нужно стилизовать дочерний компонент? Логично передать ему какой-нибудь класс и в родителе прописать стили. Решается данная задача довольно просто, с помощью директивы :global()


<script>
  import Component from "./Component.svelte";
</script>

<div class="parent">
  <Component class="childClass" />
</div>

<style>
  .parent :global(.childClass) {
    color: red;
  }
</style>

Пример в REPL


При таком подходе вы все равно получите изолированный стиль, но который применяется во всей иерархии дочерних компонентов


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} />

Пример в REPL


Этот способ не совсем легальный, поскольку использует внутреннее 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);

Пример в REPL


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


Приглашаю всех принять участие!

Tags:
Hubs:
+21
Comments64

Articles