Всем бодрого времени суток! Вдогонку к моему предыдущему посту, хочу показать простой пример микро-компонента автокомплита на SvelteJS. Кто еще не успел познакомиться с данным мирко-фреймворком — велком под кат!
image

Disclaimer (Attention <sarcasm/> detected!)
Сразу заявляю, что ни один из существующий, а также фреймворков будущего, и близко не стоит рядом с $mol. Я ни в коем случае не утверждаю, что какой-либо иной фреймворк круче чем $mol, потому что с $mol просто невозможно соревноваться в крутости. Это самый крутой фреймворк из когда-либо созданных человечеством. Уверен именно $mol спасет мир!

Ну, а теперь можно поговорить о Svelte ) Так как это пятничный пост, постараюсь его сильно не затягивать.

Итак, задача сделать переиспользуемый микро-компонент автодополнения с поддержкой асинхронной подгрузки списка вариантов. Иными словами, начинаем ввод искомого значения — выпадает список вариантов, который может задаваться как статически (некий массив), а так и подгружаться динамически с сервера.

Собственно сам svelte-компонент может выглядеть так:

<!-- Autocomplete.html -->
<input list="suggestions" bind:value="search" placeholder="Start typing..." />
{{#await suggestions}}
{{then options}}
<datalist id="suggestions">
    {{#each options as option}}
    <option>{{ option }}</option>
    {{/each}}
</datalist>
{{/await}}

<script>
    export default {
        data: () => ({
            search: '',
            suggestions: []
        })
    };
</script>

Ремарка
Внимательный читатель сразу заметит своеобразный «life-hack», который тут присутствует. А именно использование html5-тега <datalist/>, который фактически и создавался для подобных вещей, однако до сих пор имеет слабую поддержку браузерами.

Хочу отметить, что использую его здесь чисто в демонстрационных целях, чтобы визуально упростить код для лучшего понимания, а также потому что конкретная реализация выпадающего списка не является целью статьи. Переделать список с <datalist/> на <ul/> с парочкой стилей не представляется сложной задачей.

В итоге, у нас имеется простенький шаблон с полем для ввода и привязанным к нему списком вариантов. Поле для ввода биндится на переменную «search» в данных компонента, а список вариантов задается динамически с помощью массива «suggestions».

Обратите внимание на {{#await /}} в шаблоне. Именно эта простая конструкция позволяет нам не беспокоиться является ли переменная «suggestions» реальным массивом или же это промис, который будет разрешен в массив.

Здесь пожалуй все, теперь перейдем к использованию:

<!-- App.html -->
<h1>Selected location: {{ location }} </h1>
<Autocomplete bind:search="location" bind:suggestions="locations" />

<script>
    import Autocomplete from './Autocomplete.html'
    import { fetchLocations } from './api.js'

    export default {
        data: () => ({
	     location: ''
	}),
	computed: {
	    locations: location => {
                return location.length >= 3 ? fetchLocations(location) : []
            }
        },
	components: {
            Autocomplete
        }
    };
</script>

Это некий родительский компонент, который реализует domain-логику конкретного кейса. В данном случае — это выбор локации (города).

Сначала подключается компонент автодополнения, а также некий модуль, реализующий взаимодействие с серверным API. У нас также есть переменная «location», куда мы собственно сохраняем выбранное значение. Она биндится на «search» из компонента автокомплита через props, а также выводится неким результирующим значением чуть выше.

Далее интересный момент. Так как Svelte поддерживает вывод асинхронных значений через {{#await /}}, а также вычисляемые свойства (computed props), которые могут зависеть от других данным, мы можем написать супер простое вычисляемое свойство для получения списка вариантов с сервера и подвязать его на изменение «location». Т.е. когда пользователь вводит значение в поле, реактивная переменная «location» изменяется, что приводит к пересчету вычисляемого свойства «locations». Данное свойство также биндится на «suggestions» из компонента автокомплита через props. И все работает как «магия»))))

Отмечу также, что для того, чтобы пример работал хорошо, необходима реализация метода debounce, чтобы не завалить сервер лишними запросами. В данном случае, подразумевается, что debounce уже реализуется внутри функции fetchLocations(), потому как он не имеет прямого отношения к примеру.

Вот так вот, супер просто можно реализовать переиспользуемый микро-компонент автодополнения на Svelte.

              
              

Далее, так как к предыдущей статетье про Svelte было задано много вопросов, касающихся принципа работы Svelte, AoT-компиляции svelte-компонентов и вероятным дублированием кода. Представляю вольный перевод вот этого комментария создателя Svelte Рича Харриса (Rich Harris):

Это хороший вопрос. Я бы ответил на него несколькими способами:

  1. Часть кода переиспользуется между компонентами (например, такие методы как detachNode и др.) если мы компилируем код с помощью флага shared: true (что происходит автоматически при использовании интеграций с билд-тулзами, таких, как svelte-loader или rollup-plugin-svelte). В том случае, все эти штуки не дублируются, уменьшая накладные расходы.
  2. Код генерируется так, чтобы быть более-менее удобочитаемым, но при этом иметь довольно не большой размер.
  3. Тем не менее, в теории есть точка перехода, где размер генерируемого кода превышает размер фреймворка + шаблонов. Однако к тому времени, когда ваше приложение вырастет до такого размера вам скорее всего понадобиться использовать техники разделения кода (code-splitting). Например, подгружать компоненты асинхронно, на основании текущего URL. Методы разделения кода работают намного лучше со Svelte, чем с традиционными фреймворками, потому что традиционно, даже самый маленький кусок кода зависит от всей библиотеки.
  4. Размер кода — это лишь одна их целей Svelte. Другими целями являются высокая производительность, обеспечение хорошего опыта для разработчиков (так как компилятор основан на статическом анализе кода, мы можем иметь полезные сообщения об ошибках и другие виды предупреждений, как в Elm). Также большой плюс такого подхода отсутствие необходимости говорить «нет» хорошим идеям новых фич для фреймворка, потому как появление новых возможностей никак не влияет на людей, которые их не используют. Традиционные фреймворки всегда вынуждены искать баланс между размерами фреймворка и потребностями его пользователей. И так далее.
  5. В любом случае мы можем и будем уменьшать размер генерируемого кода, например с помощью более умной обработки пробелов или использования innerHTML в тех случаях, когда есть большой кусок статической разметки, который не имеет смысла генерировать программно. В итоге, размер генерируемого кода будет лишь уменьшаться с развитием компилятора.

Конечно же есть компромиссы, например, относительно Ractive — это гибкость. Мы не можем сделать this.observe('some.nested.property', () => {}) или использовать адапторы для сложных переменных, или иметь {{#with ...}} блоки в шаблонах и другие вещи. Все это имеет смысл в Ractive, который использует философию «делай что я имею ввиду». В то время как Svelte базируется на статическом анализе и больше на том что «делай что я говорю», что в конце концов это более строгий вариант.

От себя хочу добавить, что еще не пробовал Svelte на более-менее крупном проекте (хотя и планирую). Также я не противопоставляю его таким гигантам как Angular или даже Vue. На том поле скорее играет Ractive, чем Svelte.

Однако, считаю что он прекрасно занимает нишу небольших (виджеты и т.п.) и средних проектов (мобильные и ТВ приложения и т.п.). В таких проектах Svelte несомненно обеспечивает меньший размер бандла и большую скорость работы. Если для вас это актуально, вам стоит познакомиться с ним поближе.

Всем хороших выходных! И да прибудет с вами Сила пятницы!

UPDATE:
Для тех, кто обеспокоен дублированием кода в Svelte. Есть новая информация непосредственно от Рича Харриса. Мой комментарий на эту тему можно прочитать тут.

Комментарии (6)


  1. ZoNT
    22.12.2017 13:42

    Для сравнения то же самое, но на Vue: jsfiddle.net/gm739490


    1. PaulMaly Автор
      22.12.2017 14:19

      Спасибо за пример. Также для сравнения, вот ваш же пример на Ractive: jsfiddle.net/48vv71nm
      Кстати, без необходимости использовать async/await, которые вобщем-то еще не ready-to-use. Но это не суть.

      Суть в том, что код на Vue и Ractive не только получился значительно более грамоздким, но и в том, что чтобы данный код заработал, вам все равно придется тащить с собой весь фреймворк (70Кб в случае с Vue и еще больше Ractive). Вес данного компонента на Svelte порядка 1Кб (на самом деле чуть больше) и это все что нужно, чтобы его использовать.

      Ну и моя ремарка в конце статьи. Я ни в коем случае не утверждаю, что Svelte идеальный фреймворк для любых задач. Сам в работе в основном использую Ractive, на котором можно написать что угодно. Однако я нашел для себя области применения Svelte с которыми он справляется лучше. Плюс о нем мало пишут в России и как мне кажется зря, поэтому как могу стараюсь донести информацию.

      Ну и опять же скорость.


      1. ZoNT
        25.12.2017 16:22

        Ну и если заглянуть в код приложений, то они разные: в одном используется вотчер для записи в локалсторедж, в другом — нет никакого вотчера и запись в локалсторажд размазана по всему коду. И т.д.
        И на Vue можно написать так, чтобы работало в бенчмарках супербыстро, только выглядеть такой код будет непрезентабельно, неподдерживаемо и плохорасширяемо.


        1. PaulMaly Автор
          25.12.2017 18:59

          И на Vue можно написать так, чтобы работало в бенчмарках супербыстро,

          Не думаю что можно сделать настолько быстрее:


          Хотя конечно соглашусь, что от написания кода многое зависит.

          только выглядеть такой код будет непрезентабельно, неподдерживаемо и плохорасширяемо.

          На Svelte можно всегда писать код, который будет выглядеть презентабильно, а потом просто компилировать его в непрезентабильный, но быстрый))


  1. ebt
    23.12.2017 01:33

    Справедливости ради, реализация на VanillaJS (5 Kb):


    https://github.com/Pixabay/JavaScript-autoComplete


    1. PaulMaly Автор
      23.12.2017 02:43

      Немного не понял к чему вы это? Так то Svelte — это тоже чистая ванилла. Вы просто пишете в компонентном стиле, а потом все компилируется в ваниллу.

      Тот факт, что моя реализация меньше по размеру чем та, что привели вы, означает лишь то, что данная реализация более функциональная, чем моя. Все же это демо пример фреймворка, а не ready-to-use решение. Вообще кстати ниче такая штука, спасибо!