Демонстрация основных возможностей Svelte.

Svelte был признан разработчиками самым любимым веб-фреймворком в 2021 году (ссылка). Так что же такое Svelte и почему его так любят?

Svelte — это довольно уникальный фреймворк JavaScript, цель которого — быть «по-настоящему реактивным» и помогать разработчикам «писать меньше кода».

Он имеет замечательную функцию создания крошечных пакетов кода, заставляя сам фреймворк «исчезать» на этапе компиляции, отправляя оптимизированный ванильный код JavaScript, а не используя большие и сложные библиотеки, загружаемые во время выполнения.

На мой взгляд, именно это меняет правила игры и выделяет Svelte среди других фреймворков JavaScript. Крошечный размер пакета означает гораздо более быстрое время загрузки, что, похоже, является направлением, в котором движется Интернет, поскольку все больше и больше данных показывают преимущества быстрого веб-сайта. Этот этап компиляции также устраняет необходимость в таких методах, как виртуальный DOM, используемый React и Vue.js, что еще больше увеличивает скорость веб-сайта.

Еще одной особенностью является отсутствие шаблона. Svelte очень близок к стандартной веб-разработке, где компоненты могут выглядеть точно так же, как обычный HTML. Я уверен, что это важная причина, по которой разработчики любят этот фреймворк.

Чтобы представить Svelte, давайте воспользуемся PokeAPI для создания простого одностраничного приложения, в котором пользователи могут выбирать покемонов, с живой панелью поиска для фильтрации списка всех покемонов. Это продемонстрирует все основные функции Svelte полезным способом. Полный код можно найти здесь.

Монтаж

Давайте сначала установим базовый Svelte. Для этого выполните следующую команду:

npx degit sveltejs/template new-svelte-project

Это скопирует начальный шаблон svelte в нужную папку. Чтобы включить TypeScript, перейдите в новую папку svelte и запустите:

node scripts/setupTypeScript.js

Теперь все, что вам нужно сделать, это установить необходимые файлы, запустив

npm install

Характеристики компонентов

Компонент svelte — это файл, оканчивающийся на .svelte.
Как видно из App.svelte, компонент Svelte может быть довольно простым и состоять из трех частей. HTML, тег script для размещения вашего javascript и тег стиля для размещения CSS.
Это похоже на Vue, только без стандартного кода.

Очистите скрипт и HTML-содержимое App.svelte и давайте использовать данный CSS в теге стиля.

Переменные и реактивность

Переменные создаются в теге script.
Мы можем очень легко создать строковую переменную и отобразить ее в DOM с помощью фигурных скобок {}.

<! — For example →
<script lang=”ts”>
 let name: string = ‘pokemon searcher’
</script>
<h1>{name}</h1>

Для поиска по именам покемонов нам понадобится поле ввода и переменная, содержащая содержимое этого поля.
Чтобы сделать переменную 'pokemonName' равной содержимому поля ввода, мы можем использовать специальный svelte ключевое слово «bind», которое включает двустороннюю привязку переменной «pokemonName».

<!-- App.svelte -->
<script lang="ts">
  let pokemonName: string = ''
</script>
<main>
  <span>Search: </span>
  <input type="text" bind:value="{pokemonName}" />
<h1>Pokemon: {pokemonName}</h1>
</main>

Теперь ввод в поле ввода изменяет вывод названия покемона.
Это ключевое слово bind обеспечивает двустороннюю привязку без использования функции onInput, которая изменяет значение переменной pokemonName, как в React.

onMount и асинхронная выборка

Для этого примера приложения мы сохраняем имена покемонов из PokeAPI в переменной в виде массива строк.

Мы хотим получить данные и сопоставить их, как только компонент будет отрендерен.
Для этого мы можем использовать onMount — гибкую функцию жизненного цикла, которая запускается после того, как компонент впервые отрендерен в DOM. Давайте используем это для извлечения из PokeAPI и сопоставления его с массивом имен покемонов.

<!-- App.svelte - script tag -->
<script lang="ts">
  import { onMount } from 'svelte'
  let pokemonName: string = ''
  // Fetch from api then store the mapped names.
  let pokemonData: string[] = []
  onMount(() => {
    const setPokemonData = async (): Promise<void> => {
      const rawPokemonData = await (
        await fetch('https://pokeapi.co/api/v2/pokemon?limit=99')
      ).json()
      pokemonData = rawPokemonData.results.map(
        (p: { name: string; url: string }) => p.name
      )
    }
    setPokemonData()
  })
</script>

Теперь у нас есть список имен покемонов в массиве pokemonData, который мы можем использовать в нашем простом проекте.

Реактивные объявления

Для функции поиска в реальном времени нам нужен массив, содержащий элементы, отфильтрованные по пользовательскому вводу из имен покемонов.

В Svelte есть отличный инструмент для работы с состояниями, производными от других свойств — реактивными объявлениями.
Они выглядят так.

$: reactiveVar = otherVar * 2;

Теперь «reactiveVar» — это переменная, но ее значение вычисляется каждый раз, когда изменяется переменная «otherVar» (svelte запускает вычисление при изменении переменных, используемых в этом вычислении).

Мы можем превратить переменную, содержащую отфильтрованные имена покемонов, в реактивное объявление. Мы назовем это «предложениями».

<!-- App.svelte - bottom of script tag -->
<script>
  // ...

  let suggestions: string[]
  $: suggestions = 
       pokemonName.length > 0
         ? pokemonData.filter(
             (name) => name.includes(pokemonName)
           )
         : pokemonData
</script>

Итак, «предложения» — это массив имен покемонов, в который входит строка, введенная в поле ввода.

Реактивное присваивание не работает с машинописным текстом, поэтому мы можем объявить переменную «предложения», как обычно, чтобы сохранить проверки типов.

Петли

Мы хотим отобразить содержимое этого массива «предложений» на странице, и мы можем сделать это с помощью цикла svelte. В Svelte есть специальное ключевое слово «каждый», которое позволяет нам отображать элементы DOM для каждого элемента в данной итерации.
Чтобы отобразить имя каждого покемона, просто используйте каждое ключевое слово для циклической обработки переменной «pokemonData».

<!-- App.svelte - html -->
<main>
  <span>Search: </span>
  <input type="text" bind:value="{pokemonName}" />

  {#each suggestions as suggestion}
    <h2>{suggestion}</h2>
  {/each}
</main>

Когда мы вводим текст в поле ввода, мы видим, как меняется список предложений. Довольно круто для такого простого кода.

Условный рендеринг

У Svelte есть и другие ключевые слова. Еще один полезный — #if. Ключевое слово #if позволяет использовать условную логику.

Например, мы можем визуализировать экран загрузки, пока получаем данные из PokeAPI.

<!-- App.svelte - html -->
<main>
  {#if pokemonData && pokemonData.length > 0}
    <span>Search: </span>
    <input type="text" bind:value="{pokemonName}" />

    {#each suggestions as suggestion}
      <h2>{suggestion}</h2>
    {/each}
  {:else}
    <h2>Loading...</h2>
  {/if}
</main>

Компоненты и реквизит

Реквизиты используются для передачи данных от одного компонента к другому. Это довольно стандартно для фреймворков, и синтаксис для этого очень прост.

Чтобы сигнализировать о том, что компонент принимает свойство, для переменной используется ключевое слово экспорта.

<script lang="ts">
  export let stringProp: string
</script>

Теперь, чтобы передать значение для «stringProp», просто используйте имя экспортируемой переменной при написании компонента.

<script lang="ts">
  import NewComponent from './NewComponent.svelte'
</script>

<NewComponent  stringProp="prop value"  />

Для нашего приложения давайте создадим компонент для каждого предложения.

Создайте новый файл «Suggestion.svelte» в src/ и просто примите и отобразите реквизит «предложение».

<!-- Suggestion.svelte -->
<script lang="ts">
  export let suggestion: string
</script>

<div class="suggestion">{suggestion}</div>

<style>
    .suggestion {
        font-size: 1.25rem;
    }
</style>

Теперь мы можем импортировать этот компонент и использовать его в нашем цикле #each.

<!-- App.svelte - top of script tag -->
<script lang="ts">
  import Suggestion from './Suggestion.svelte'

  // ...
  // ...
</script>
<!-- App.svelte - html -->
<main>
  {#if pokemonData && pokemonData.length > 0}
    <span>Search: </span>
    <input type="text" bind:value="{pokemonName}" />

    {#each suggestions as suggestion}
      <Suggestion suggestion="{suggestion}"/>
    {/each}
  {:else}
    <h2>Loading...</h2>
  {/if}
</main>

На данный момент это довольно бессмысленно, поэтому давайте добавим немного логики в компонент «Предложение» в виде событий.

Пользовательские события

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

Для нашего приложения мы хотим иметь возможность щелкнуть предложение, чтобы выбрать нашего покемона. Мы можем сделать это, отправив пользовательское событие из компонента «Предложение» в компонент приложения, а затем установив значение переменной, которая содержит выбранного нами покемона.

Сначала создайте новую переменную «chosenPokemon» и отобразите ее на экране в App.svelte.

<!-- App.svelte - bottom of script tag -->
<script lang="ts">
  // ...
  // ...

  let chosenPokemon: string = ''
</script>
<!-- App.svelte - html -->
<main>
  {#if pokemonData && pokemonData.length > 0}
    <h1>Chose Your Pokemon</h1>
    <h2>Chosen Pokemon: {chosenPokemon}</h2>

    <div>
      <span>Search: </span>
      <input type="text" bind:value="{pokemonName}" />

      {#each suggestions as suggestion}
        <Suggestion suggestion="{suggestion}"/>
      {/each}
    </div>
  {:else}
    <h2>Loading...</h2>
  {/if}
</main>

Теперь в Suggestion.svelte мы можем отправлять пользовательское событие «chosePokemon» при нажатии на предложение.

Чтобы создать собственное событие, нам нужно импортировать createEventDispatcher из svelte.

<!-- Suggestion.svelte - script tag -->
<script lang="ts">
  import { createEventDispatcher } from 'svelte'

  export let suggestion: string

  // Function to dispatch a custom event.
  const dispatch = createEventDispatcher()
  const chosePokemon = (): void => {
    dispatch('chosePokemon', {
      pokemon: suggestion
    })
  }
</script>

Теперь у нас есть функция «chosePokemon», которая отправляет пользовательское событие «chosePokemon» родительскому компоненту.

Чтобы вызвать эту функцию при нажатии на предложение, нам нужно использовать svelte событие «on: click», подобное этому.

<!-- Suggestion.svelte - html -->
<div class="suggestion" on:click="{chosePokemon}">
  {suggestion}
</div>

Вернувшись в файл App.svelte, мы можем обработать это пользовательское событие, используя синтаксис «on: (имя события)».

<!-- App.svelte - 'Suggestion' component in html -->
<Suggestion
  suggestion="{suggestion}"
  on:chosePokemon="{(e) => {
    chosenPokemon = e.detail.pokemon
  }}"
/>

Этот обработчик устанавливает значение переменной selectedPokemon равным имени покемона, переданному в пользовательском событии (находится в свойстве «detail»). Когда мы нажимаем на предложение, отображается имя этого покемона.

Я установил переменную «chosenPokemon» таким образом, чтобы ввести пользовательские события, однако есть гораздо более чистый и простой способ сделать это: переадресация привязки.

Привязать переадресацию

Мы видели, как ключевое слово bind использовалось для настройки двусторонней привязки при создании поля ввода, но это ключевое слово также можно использовать в наших компонентах.

В App.svelte мы можем заменить обработчик события selectedPokemon на ключевое слово bind для свойства selectedPokemon.

<!-- App.svelte - 'Suggestion' component in html -->
<Suggestion suggestion="{suggestion}" bind:chosenPokemon />

И в компоненте «Предложение» мы можем принять эту поддержку и сделать так, чтобы функция «on: click» просто задавала эту переменную «chosenPokemon».

<!-- Suggestion.svelte -->
<script lang="ts">
  export let suggestion: string
  export let chosenPokemon: string = ''
</script>

<div 
  class="suggestion" 
  on:click="{() => chosenPokemon = suggestion}"
>
  {suggestion}
</div>

Теперь у нас есть та же функциональность, что и раньше, но только часть кода.

магазины

Я хочу подвести итог, представив магазины.

Со Svelte нам не нужно использовать внешнюю библиотеку, такую ​​как Redux, чтобы иметь центральное хранилище, она поставляется с фреймворком.

По сути, хранилище — это объект с методом подписки, который позволяет нашим компонентам Svelte получать уведомления о любых изменениях значения хранилища. Svelte определяет 2 разных типа хранилищ: хранилище с возможностью записи и хранилище с возможностью чтения. Как следует из названий, доступное для записи хранилище разрешает чтение и запись, а доступное для чтения хранилище позволяет только чтение.

Для нашего приложения мы отправим в магазин переменную pokemonData. Поскольку эта переменная просто собирает и хранит данные о покемонах, мы будем использовать удобочитаемое хранилище.

Во-первых, нам нужен новый файл в папке src (я назову его store.ts).
Мы можем импортировать функцию readable store из svelte вместе с необходимыми типами.

// stores.ts
import { readable, Readable, Subscriber } from 'svelte/store'

Читаемая функция в качестве первого аргумента принимает начальное значение хранилища, а в качестве второго аргумента — функцию «старт». Эта «стартовая» функция вызывается, когда магазин получает своего первого подписчика, поэтому мы будем получать данные нашего API.

Функция получает функцию обратного вызова «установить», которая используется для установки значения хранилища, и возвращает функцию «стоп», которая вызывается, когда последний подписчик отписывается (где мы можем выполнить некоторую очистку).

В нашем магазине мы можем просто скопировать содержимое нашей функции setPokemonData, но вместо присвоения значения pokemonData мы вызываем функцию set.

import { readable, Readable, Subscriber } from 'svelte/store'

const setPokemonData = 
  async (set: Subscriber<string[]>): Promise<void> => {
    const rawPokemonData = await (
      await fetch('https://pokeapi.co/api/v2/pokemon?limit=99')
    ).json()

    set(
      rawPokemonData.results.map(
        (p: { name: string; url: string }) => p.name
      )
    )
  }

// Export the new store 'pokemonData' variable.
export const pokemonData: Readable<string[]> = 
  readable([], (set) => {
    setPokemonData(set)

    return () => set([])
  })

Вот и все. Теперь у нас есть центральное хранилище, в котором хранятся имена наших покемонов в «pokemonData».

Чтобы использовать наш магазин, нам нужно импортировать переменную pokemonData из файла нашего магазина. Затем мы можем использовать специальный тонкий символ «$» для ссылки на стоимость магазина.

<!-- App.svelte -->
<script lang="ts">
  import { pokemonData } from './stores.js'
  import Suggestion from './Suggestion.svelte'

  let pokemonName: string = ''

  let suggestions: string[]
  // $pokemonData instead of pokemonData
  $: suggestions =
    pokemonName.length > 0
      ? $pokemonData.filter((name) => 
          name.includes(pokemonName)
        )
      : $pokemonData

  let chosenPokemon: string = ''
</script>

<main>
  {#if $pokemonData && $pokemonData.length > 0}
    <h1>Chose Your Pokemon</h1>
    <h2>Chosen Pokemon: {chosenPokemon}</h2>

    <div>
      <span>Search: </span>
      <input type="text" bind:value="{pokemonName}" />
      {#each suggestions as suggestion}
        <Suggestion 
          suggestion="{suggestion}" 
          bind:chosenPokemon 
        />
      {/each}
    </div>
  {:else}
    <h2>Loading...</h2>
  {/if}
</main>

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

Теперь, хотя в svelte есть доступные для записи и чтения хранилища, все, что привязано к контракту хранилища svelte и реализует метод подписки, является хранилищем. Это означает, что магазины очень гибкие и могут быть адаптированы к вашим потребностям. Вы даже можете создать магазин на другом языке, например, на Rust, как показано здесь.

Заключительные примечания

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

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

Svelte уже некоторое время является одним из самых любимых фреймворков, даже несмотря на то, что его использование растет, что является потенциальным признаком того, что он станет таким же большим, как Vue или React. Это совпадает с недавним стремлением к более производительной сети, переходя от сумасшедших больших пакетов JavaScript, предоставляемых клиенту, к серверному или гибридному рендерингу.

Команда Svelte сейчас работает над SvelteKit, версией Next.js для Svelte, о которой вы можете узнать здесь:



Если вам понравилась эта статья, поделитесь ею.

Посмотрите мой GitHub и другие статьи.

Первоначально опубликовано на https://dev.to 20 марта 2022 г.

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.