1Kb autocomplete

в 8:53, , рубрики: autocomplete, javascript, ractive, svelte

Всем бодрого времени суток! Вдогонку к моему предыдущему посту, хочу показать простой пример микро-компонента автокомплита на 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.

              
              1Kb autocomplete - 2

Далее, так как к предыдущей статетье про 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. Есть новая информация непосредственно от Рича Харриса. Мой комментарий на эту тему можно прочитать тут.

Автор: PaulMaly

Источник

Поделиться

* - обязательные к заполнению поля