Посібник зі Svelte

26 хв. читання
19 листопада 2019

Якщо ви досі не працювали зі Svetle, то це чудова можливість почати. Матеріал призначений для розробників, які раніше працювали з фреймворком на зразок Vue чи React і шукають новий підхід. Svelte вартий уваги, тому що він відрізняється від інших фреймворків і має унікальні фічі для вебу.

Вступ до Svelte

Svelte — веб-фреймворк, який пропонує свіжий погляд на те, як створювати веб застосунки.

Посібник зі Svelte

Якщо ви вже мали досвід з React, Vue, Angular чи іншим фронтенд-фреймворком, Svelte може вас приємно здивувати.

Після знайомства зі Svelte вам здаватиметься, що це, скоріше, чистий JavaScript. Звичайно, є деякі відмінності, на зразок шаблонів, які більше нагадують HTML. Однак зі Svelte ви забудете про труднощі, які виникали в інших фреймворках.

На відміну від React, Vue, Angular та інших фреймворків, застосунок на Svelte заздалегідь компілюється. Так значно покращується користувацький досвід, адже інтерфейс працює плавно. При розгортанні вашого застосунку ви отримуєте чистий (та швидкий) JavaScript.

Початок роботи зі Svelte

Для початку необхідно встановити Node.js, на якому базуються всі інструменти. Для цього скористайтесь посібником і переконайтесь, що маєте останню версію (як оновити Node.js).

Якщо ви не хочете встановлювати Node, веб-сайт Svelte пропонує інтерактивне середовище програмування. Там зручно тестувати невеликі застосунки на Svelte та експериментувати.

З Node встановлюється зручна команда npx. Одразу використаємо її:

npx degit sveltejs/template firstapp

Так ми налаштуємо degit, який завантажить останню версію шаблона проєкту Svelte з репозиторію в щойно створену теку firstapp.

Переконайтеся, що на комп'ютері встановлений git, а також визначена PATH-змінна. Інакше degit не працюватиме. Якщо ж після всіх кроків у вас все ще не працює проєкт, ви можете клонувати його з репозиторію, а потім видалити приховану теку .git. По суті, це те ж саме, що робить degit (різниця лише у тому, що тека називатиметься template, а не firstapp).

Перейдемо до теки firstapp та запустимо npm install, щоб завантажити додаткові залежності проєкту. Під час написання матеріалу, залежності були такі:

"npm-run-all"
"rollup"
"rollup-plugin-commonjs"
"rollup-plugin-livereload"
"rollup-plugin-node-resolve"
"rollup-plugin-svelte"
"rollup-plugin-terser"
"svelte"

Як бачимо, це основні пакети Svelte, а також Rollup(альтернатива Webpack) та деякі його плагіни. npm-run-all — CLI-інструмент, який дозволяє запускати декілька npm-скриптів паралельно чи послідовно.

Ми вже готові запустити наш проєкт на Svelte в режимі розробки, виконавши:

npm run dev

Застосунок за замовчуванням запускається на localhost з портом 5000:

Посібник зі Svelte

Якщо відкрити зазначену адресу у браузері, можна побачити приклад «Hello world» :

Посібник зі Svelte

Відкриємо код в улюбленому редакторі. В теці src є файл main.js з основними налаштуваннями застосунку:

Посібник зі Svelte

Це точка входу застосунку, а також ініціалізація компонента App, визначеного у файлі App.svelte:

<script>
export let name;
</script>

<style>
h1 {
color: purple;
}
</style>

<h1>Hello {name}!</h1>

Компоненти Svelte

Сучасна веб-розробка орієнтована на компонентний підхід, Svelte також.

Що таке компонент? Компонент — атомарна та автономна частина застосунку, яка за необхідністю посилається на інші компоненти для формування інтерфейсу.

Іншими словами, це окремий складник застосунку. Компонентом може бути форма, поле вводу або ж увесь застосунок.

Компоненти Svelte містять все необхідне для відображення частини UI. Кожен компонент визначений у файлі з розширенням .svelte. Там є розмітка (HTML), поведінка компонента (JavaScript), та його зовнішній вигляд (CSS). І все це без потреби створювати окремі файли.

Такий підхід до організації компонента досить зручний, адже немає потреби шукати елементи по декількох файлах.

Розглянемо приклад компонента у файлі Dog.svelte:

<script>
export let name;
</script>

<style>
h1 {
  color: purple;
}
</style>

<h1>The dog name is {name}!</h1>

Будь-який JavaScript-код повинен бути між тегами script:

Місце CSS — між тегами style. Так зоною видимості стилів стає компонент, тому вони не «витікають» назовні. Тобто якщо інший компонент міститиме тег h1, то ці стилі не впливатимуть на нього. Такий підхід дуже корисний для повторного використання компонентів, які ви вже створювали для інших застосунків, або якщо ви додаєте open-source бібліотеки.

Імпорт компонентів у інші компоненти

Як ми вже згадували, компонент може бути використаний іншими компонентами. Наприклад, певний компонент може імпортувати компонент Dog у свій код.

Припустимо, що компонент House розташований у файлі House.svelte, у тій самій теці, що й Dog.svelte.

<script>
import Dog from './Dog.svelte'
</script>

Тепер ви можете використовувати компонент Dog як HTML-тег:

<script>
import Dog from './Dog.svelte'
</script>

<Dog />

Експорт певних функцій з компонента

З прикладу вище ми бачимо, що для експорту компонента не треба робити зайвих кроків. Сам по собі компонент вже експортується за замовчуванням.

Як щодо експорту чогось окрім розмітки та функціоналу компонента?

Для цього треба додати до тегу script атрибут context="module".

Розглянемо приклад. Припустимо, у вас є компонент Button у файлі Button.svelte:

<button>A button</button>

Ви хочете, щоб інші компоненти могли впливати на колір кнопки.

Найкраще рішення для цього — використовувати props (детальніше в наступному розділі).

Спочатку ми можемо визначити функцію changeColor. Додамо їй певну логіку та експортуємо у тегу script:

<script context="module">
export function changeColor() {
  //...логіка зміни кольору..
}
</script>

<button>A button</button>

Зверніть увагу, що ви можете мати також «звичайний» тег script в компоненті.

Тепер інші компоненти можуть імпортувати як Button, так і функцію changeColor:

<script>
import Button, { changeColor } from './Button.svelte'
</script>

Обробка стану у Svelte

Кожен компонент (окрім розмітки, CSS та логіки JavaScript) може містити власний стан.

Що таке стан? Стан — дані, необхідні для рендерингу компонента. Наприклад, якщо поле вводу форми містить рядок «test», то десь визначена змінна, яка зберігає цей стан. Чекбокс чи радіокнопка вибрані? Потрібна змінна, щоб зафіксувати цей факт.

Стан зберігається в частині script компонента:

<script>
let count = 0
</script>

Якщо у вас був досвід з Vue чи React, то вам, мабуть, цікаво, як оновити це значення. Svelte зручний тим, що вам не треба робити зайвих дій, аби оновити стан компонента.

Все що потрібно — присвоїти цей стан за допомогою оператора =. Припустимо, є змінна count. Ви можете збільшити її виразом count = count + 1 або count++:

<script>
let count = 0

const incrementCount = () => {
  count++
}
</script>

{count} <button on:click={incrementCount}>+1</button>

А в React, наприклад, вам довелося б викликати this.setState() або використати хук useState().

У Vue більш структурований підхід з використанням класів та властивості data.

У порівнянні з іншими фреймворками, Svelte більше нагадує JavaScript-стиль. Варто пам'ятати про одну річ: ми повинні також зробити присвоєння при зміні змінної. В іншому випадку Svelte не розпізнає, що стан змінився.

Для простих значень на зразок рядків чи чисел все вже задано, адже всі методи рядків повертають нові рядки, і те ж саме для чисел, тобто вони незмінні.

Як на рахунок масивів? Ми не можемо використовувати методи, які змінюють масив (на зразок push(), pop(), shift(), splice()), тому що вони не присвоюють нові значення, а змінюють внутрішню структуру даних, однак Svelte не може це розпізнати.

Тож усе-таки ви можете використовувати ці методи, однак потім вам треба перепризначити змінну в такий спосіб:

let list = [1, 2, 3]
list.push(4)
list = list

Здається, що все не дуже інтуїтивно, однак до цього можна швидко звикнути.

Інший варіант — використання оператора spread:

let list = [1, 2, 3]
list = [...list, 4]

Реактивність Svelte

У Svelte ви можете відстежувати зміни стану компонента та оновлювати інші змінні.

Наприклад, якщо у вас є змінна count:

<script>
let count = 0
</script>

і ви оновлюєте її при кліку на кнопку:

<script>
let count = 0

const incrementCount = () => {
  count = count + 1
}
</script>

{count} <button on:click={incrementCount}>+1</button>

Ви можете відстежувати зміни count, використовуючи спеціальний синтаксис $:. Так ви визначаєте новий блок коду, який Svelte виконає повторно, якщо будь-яка зазначена в ньому змінна модифікується:

Поглянемо на приклад:

<script>
let count = 0

const incrementCount = () => {
  count = count + 1
}

$: console.log(`${count}`)
</script>

{count} <button on:click={incrementCount}>+1</button>

Ми використали блок:

$: console.log(`${count}`)

Можемо не обмежуватись одним блоком:

<script>
$: console.log(`the count is ${count}`)
$: console.log(`double the count is ${count * 2}`)
</script>

А також згрупувати декілька виразів в одному блоці:

<script>
$: {
  console.log(`the count is ${count}`)
  console.log(`double the count is ${count * 2}`)
}
</script>

Для прикладу ми взяли звичайний виклик console.log(), однак ви можете оновити також інші змінні:

<script>
let count = 0
let double = 0

$: {
  console.log(`the count is ${count}`)
  double = count * 2
  console.log(`double the count is ${double}`)
}
</script>

Props у Svelte

Ви можете імпортувати компонент Svelte в інший компонент, використовуючи синтаксис import ComponentName from 'componentPath':

<script>
import SignupForm from './SignupForm.svelte';
</script>

Шлях відносний щодо поточного компонента. Сполучення ./ означає «поточна тека». Якщо необхідно піднятись на одну теку вище — використовуйте таке позначення: ../.

Тепер ви можете застосовувати щойно імпортований компонент в розмітці як HTML-тег:

<SignupForm />

Між компонентами формується відношення «батьківський-дочірній»: один імпортує, інший імпортований.

Часто необхідно, щоб батьківський компонент передавав дані дочірньому компоненту. Для цього можна використати props. Це щось подібне до атрибутів в чистому HTML для однонапрямної взаємодії між компонентами.

В наступному прикладі ми передаємо prop disabled, визначивши для нього булеве значення true:

<SignupForm disabled={true}/>

В компоненті SignupForm вам необхідно експортувати prop в такий спосіб:

<script>
  export let disabled
</script>

Так ми вказуємо, що prop передається батьківським компонентом.

При використанні компонента ви можете передати змінну замість значення, а тоді змінювати її динамічно:

<script>
import SignupForm from './SignupForm.svelte';
let disabled = true
</script>

<SignupForm disabled={disabled}/>

Коли значення disabled змінюється, дочірній компонент оновиться відповідно до нового значення. Наприклад:

<script>
import SignupForm from './SignupForm.svelte';
let disabled = true
setTimeout(() => { disabled = false }, 2000)
</script>

<SignupForm disabled={disabled}/>

Управління станом між компонентами у Svelte

Ми вже розглянули, як легко Svelte оброляє стан одного компонента. А як щодо передачі стану між декількома компонентами?

Поширення стану за допомогою props

Це досить звичайна річ серед інших UI-фреймворків. Основний принцип — виносити стан наверх. Коли компоненту треба поділитися даними з іншими компонентами, стан можна винести до спільного для дочірніх батьківського компонента.

Стан передається деревом вниз, поки не знаходить ті компоненти, які потребують цієї інформації.

Описаний алгоритм працює за допомогою props, і найбільша перевага такого підходу — його простота.

Context API

Однак часом потрібен інший підхід. Наприклад, якщо два компоненти розташовані так далеко, що нам довелось би виносити стан у найвищий компонент в ієрархії.

В таких випадках можна скористатися іншою технікою — context API. Це ідеальний підхід, коли треба, щоб декілька компонентів обмінювались даними з дочірніми компонентами, але без використання props.

Context API забезпечується двома функціями з пакета svelte: getContext та setContext:

Ви ініціалізуєте контекст певним об'єктом, визначивши для нього ключ:

<script>
import { setContext } from 'svelte'

const someObject = {}

setContext('someKey', someObject)
</script>

В іншому компоненті ви можете викликати getContext, щоб отримати об'єкт за ключем:

<script>
import { getContext } from 'svelte'

const someObject = getContext('someKey')
</script>

Ви можете використовувати getContext лише для того, щоб отримати ключ в компоненті, який встановлює контекст з setContext або в його нащадках.

Якщо є компоненти з різних дерев і ви хочете, щоб вони обмінювались даними, використовуйте сховища.

Використання сховищ (stores) Svelte

Сховища Svelte — чудовий інструмент для обробки стану застосунку, коли не бажано, щоб компоненти часто обмінювались props.

Спочатку потрібно імпортувати writable з svelte/store:

import { writable } from 'svelte/store'

та створити змінну сховища за допомогою функції writable(), передавши значення за замовчуванням як перший аргумент:

const username = writable('Guest')

Ви можете винести частину коду зі сховищем в окремий файл (наприклад store.js, .js, тому що це компонент Svelte) та імпортувати його в декілька компонентів.

import { writable } from 'svelte/store'
export const username = writable('Guest')

Будь-який інший компонент, який завантажить цей файл тепер зможе отримати значення зі сховища:

<script>
import { username } from './store.js'
</script>

Значення імпортованої змінної можна змінити за допомогою set():

username.set('new username')

А оновити за допомогою функції update(). Вона відрізняється від set() тим, що ви передаєте не просто нове значення, а колбек-функцію з поточним значенням як аргумент:

const newUsername = 'new username!'
username.update(existing => newUsername)

Можемо додати більше логіки:

username.update(existing => {
  console.log(`Updating username from ${existing} to ${newUsername}`)
  return newUsername
})

Щоб одноразово отримати значення зі сховища, ви можете експортувати функцію get() з svelte/store.

import { writable, get } from 'svelte/store'
export const username = writable('Guest')
get(username) //'Guest'

Щоб створити реактивну змінну, яка оновлюватиметься при модифікації змінної сховища, ви можете додати на початок назви змінної символ $ (в нашому прикладі це $username). Ваш компонент буде повторно рендеритись при змінах у сховищі.

Зверніть увагу: Svelte розглядає символ $ як зарезервоване значення, та не дозволить використовувати його для змінних, що не стосуються сховища (а це може збивати з пантелику). Тож якщо ви звикли називати змінні, що посилаються на DOM з $, не робіть цього у Svelte.

Існує й інший варіант, якщо вам треба виконати певну логіку при зміні значення змінної — метод subscribe().

username.subscribe(newValue => {
  console.log(newValue)
})

Окрім сховищ зі змінюваними даними, Svelte пропонує також два додаткових види сховищ: сховища читання та сховища наслідування.

Сховища читання у Svelte

Сховище читання відрізняється тим, що його не можна оновити зовні — там немає методів set() та update(). Після того як ви визначили початкове значення, змінити його вже не можна.

Офіційна документація Svelte показує цікавий приклад таймера. Окрім встановлення таймера, можна також отримувати певні ресурси з мережі, робити виклики API, отримувати дані файлової системи (використовуючи локальний Node.js сервер) тощо.

В нашому випадку ми замість ініціалізації змінної сховища з writable() використаємо readable():

import { readable } from 'svelte/store'
export const count = readable(0)

Ви також можете передати функцію, відповідальну за оновлення значення. Функція отримує метод set() для модифікації значення:

<script>
import { readable } from 'svelte/store'
export const count = readable(0, set => {
  setTimeout(() => {
    set(1)
  }, 1000)
})
</script>

Тут ми оновлюємо значення після того, як пройде секунда. Можна виконувати код через певний інтервал:

import { readable, get } from 'svelte/store'
export const count = readable(0, set => {
  setInterval(() => {
    set(get(count) + 1)
  }, 1000)
})

Таку логіку можна використовувати в іншому компоненті, імпортувавши функцію:

<script>
import { count } from './store.js'
</script>

{$count}

Сховища наслідування у Svelte

Сховища наслідування дозволяють додати нове значення до сховища, яке базується на значенні наявного сховища.

Для цього можна використати функцію derived(), експортовану зі svelte/store, яка приймає першим параметром наявне значення в сховищі, а як другий параметр — функцію, що використовує це значення:

import { writable, derived } from 'svelte/store'

export const username = writable('Guest')

export const welcomeMessage = derived(username, $username => {
  return `Welcome ${$username}`
})
<script>
import { username, welcomeMessage } from './store.js'
</script>

{$username}
{$welcomeMessage}

Слоти у Svelte

Слоти — зручний спосіб об'єднання компонентів, а ще вони корисні для налаштування імпортованих компонентів.

Пояснимо, як все працює. В компоненті ви можете визначити слот, вказавши <slot /> чи <slot></slot>.

Розглянемо компонент Button.svelte, який містить розмітку для кнопки <button>. В React це було б так: <button>{props.children}</button>.

Тепер в іншому компоненті ми можемо передати дочірні компоненти для нашого Button і вони будуть поміщені в слот.

<script>
import Button from './Button.svelte'
</script>

<Button>Insert this into the slot</Button>

Ви можете визначити для слота значення за замовчуванням, якщо не буде передбачено іншого:

<button>
 <slot>
   Default text for the button
 </slot>
</button>

В компоненті може бути декілька слотів, ви можете відрізняти їх між собою за допомогою атрибута name. Слот без назви буде використовуватись за замовчуванням:

<slot name="before" />
<button>
  <slot />
</button>
<slot name="after" />

А застосовувати це можна так:

<script>
import Button from './Button.svelte'
</script>

<Button>
  Insert this into the slot
  <p slot="before">Add this before</p>
  <p slot="after">Add this after</p>
</Button>

А так це все буде у DOM:

<p slot="before">Add this before</p>
<button>
  Insert this into the slot
</button>
<p slot="after">Add this after</p>

Події життєвого циклу компонента Svelte

Кожен компонент у Svelte має декілька етапів життєвого циклу, на кожному з них ми можемо реалізовувати певний функціонал.

Існують такі події життєвого циклу:

  • onMount — запускається після рендерингу компонента;
  • onDestroy — запускається після знищення компонента;
  • beforeUpdate — запускається перед оновленням DOM;
  • afterUpdate — запускається після оновлення DOM.

Ми можемо запланувати функції, які запускатимуться Svelte на певних етапах.

За замовчуванням ми не маємо доступу до перелічених методів, однак нам треба імпортувати їх з пакета svelte:

<script>
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte'
</script>

Поширений варіант використання onMount — отримання даних зі стороннього ресурсу.

Приклад використання onMount:

<script>
  import { onMount } from 'svelte'

  onMount(async () => {
    // певні дії
  })
</script>

onDestroy дозволяє очистити дані або зупинити операцію, яку ми запустили при ініціалізації компонента (на зразок таймерів або setInterval).

Якщо ви повертаєте функцію з onMount, вона виконуватиметься на тому ж етапі, що й onDestroy:

<script>
  import { onMount } from 'svelte'

  onMount(async () => {
    //ініціалізація компонента

    return () => {
      // після знищення компонента
    }
  })
</script>

Розглянемо практичний приклад, як запускати періодичну функцію при монтуванні та очищати інтервал після знищення компонента:

<script>
  import { onMount } from 'svelte'

  onMount(async () => {
    const interval = setInterval(() => {
      console.log('hey, just checking!')
    }, 1000)

    return () => {
      clearInterval(interval)
    }
  })
</script>

Зв'язування у Svelte

Зі Svelte ви можете створити двонапрямне зв'язування між даними та UI. Це досить поширений патерн серед сучасних веб-фреймворків, особливо корисний для форм:

bind:value

Почнемо з найбільш поширеної форми зв'язування, використовуючи конструкцію bind:value. Ви берете змінну зі стану компонента та прив'язуєте її до поля:

<script>
let name = ''
</script>

<input bind:value={name}>

Тепер якщо name зміниться, значення поля вводу зміниться відповідно. І навпаки: якщо користувач вводить певне значення в поле: змінна name оновлюється.

Пам'ятайте, що змінну потрібно оголошувати ключовим словом let/var, а не const, оскільки в останньому випадку Svelte не зможе оновити значення змінної.

bind:value працює для різних типів полів вводу (type="number", type="email" тощо), але також і для інших типів на зразок textarea та select (більше про select далі).

Чекбокси та радіокнопки

Чекбокси та радіокнопки (input-елементи з атрибутами type="checkbox" чи type="radio") дозволяють три види зв'язування:

  • bind:checked
  • bind:group
  • bind:indeterminate

bind:checked дозволяє прив'язати значення до властивості checked елемента:

<script>
let isChecked
</script>

<input type=checkbox bind:checked={isChecked}>

bind:group корисний для чекбоксів та радіокнопок, оскільки вони часто використовуються в групах. З bind:group ви можете поставити у відповідність масиву JavaScript список чекбоксів та наповнити його залежно від вибору користувача.

Розглянемо приклад. Масив goodDogs наповнюється обраними елементами:

<script>
let goodDogs = []
let dogs = ['Roger', 'Syd']
</script>

<h2>
  Who's a good dog?
</h2>

<ul>
  {#each dogs as dog}
    <li>{dog} <input type=checkbox bind:group={goodDogs} value={dog}></li>
  {/each}
</ul>

<h2>
  Good dogs according to me:
</h2>

<ul>
  {#each goodDogs as dog}
    <li>{dog}</li>
  {/each}
</ul>

Приклад можна оглянути наживо за посиланням.

bind:indeterminate дозволяє нам прив'язатись до indeterminate стану елемента (більше — за посиланням)

Select

bind:value також працює для поля типу select, щоб автоматично призначати обране значення змінній:

<script>
let selected
</script>

<select bind:value={selected}>
  <option value="1">1</option>
  <option value="2">2</option>
  <option value="3">3</option>
</select>

{selected}

Якщо ви генеруєте варіанти динамічно з масиву чи об'єктів, обраний варіант буде об'єктом, а не рядком:

<script>
let selected

const goodDogs = [
  { name: 'Roger' },
  { name: 'Syd' }
]
</script>

<h2>List of possible good dogs:</h2>
<select bind:value={selected}>
  {#each goodDogs as goodDog}
    <option value={goodDog}>{goodDog.name}</option>
  {/each}
</select>

{#if selected}
<h2>
  Good dog selected: {selected.name}
</h2>
{/if}

Перевірте за посиланням.

select також дозволяє використовувати атрибут multiple:

<script>
let selected = []

const goodDogs = [
  { name: 'Roger' },
  { name: 'Syd' }
]
</script>

<h2>List of possible good dogs:</h2>
<select multiple bind:value={selected}>
  {#each goodDogs as goodDog}
    <option value={goodDog}>{goodDog.name}</option>
  {/each}
</select>

{#if selected.length}
<h2>Good dog selected:</h2>
<ul>
  {#each selected as dog}
    <li>{dog.name}</li>
  {/each}
</ul>
{/if}

Приклад за посиланням.

Інші види зв'язування

Види зв'язувань залежать від HTML-тегів, з якими ви працюєте.

bind:files працює для полів з атрибутом type="file" для зв'язування списку обраних файлів.

HTML-елемент details дозволяє використання bind:open для зв'язування значень відкритого і закритого стану.

Медіатеги audio та video в HTML також дозволяють зв'язувати деякі властивості: currentTime, duration, paused, buffered, seekable, played, volume, playbackRate.

textContent та innerHTML можна прив'язати до contenteditable полів.

Read-only зв'язування

offsetWidth, offsetHeight, clientWidth, clientHeight зв'язування можуть використовуватись лише для читання для будь-якого блокового HTML-елемента за винятком пустих тегів (на зразок br) та елементів, які явно встановлені як рядкові за допомогою display: inline.

Отримання посилання на HTML-елемент в JavaScript

bind:this — особливий вид зв'язування, який дозволяє отримати посилання на HTML-елемент та прив'язати його до JavaScript-змінної:

<script>
let myInputField
</script>

<input bind:this={myInputField} />

Це корисно, коли вам треба виконати деяку логіку над елементом, наприклад, використовуючи колбек onMount().

Зв'язування props компонента

З конструкцією bind: ви можете прив'язати значення до будь-якого prop-компонента.

Припустимо, у нас є компонент Car.svelte:

<script>
export let inMovement = false
</script>

<button on:click={() => inMovement = true }>Start car</button>

Можна імпортувати компонент та прив'язати prop inMovement:

<script>
  import Car from './Car.svelte';

  let carInMovement;
</script>

<Car bind:inMovement={carInMovement} />

{carInMovement}

Умовні вирази в шаблонах

В компоненті Svelte при рендерингу HTML можна використати особливий синтаксис, щоб зробити UI досконалим на кожному етапі життєвого циклу застосунку.

Розглянемо умовні структури. Припустимо, вам необхідно виконати різні дії, залежно від умови. Svelte пропонує дуже потужний набір умовних структур.

Перша з них – if:

{#if isRed}
  <p>Red</p>
{/if}

Вираз стоїть між конструкцією {#if} та {/if}. В конструкції ми перевіряємо значення змінної чи виразу. Якщо змінна isRed приймає значення true, буде відповідна розмітка:

<script>
let isRed = true
</script>

Варто пам'ятати, що пустий рядок, 0 та false належать до хибних значень, а рядок з вмістом, число > 0 чи true — правдиві значення.

Якщо в умовний вираз потрапляє хибне значення, ми не побачимо розмітку.

Ми можемо використати конструкцію else, якщо треба показати розмітку при хибному значенні:

{#if isRed}
  <p>Red</p>
{:else}
  <p>Not red</p>
{/if}

Залежно від значення isRed ми побачимо одну з розміток.

Ви можете використовувати будь-який JavaScript-вираз у блоці if, а для заперечення — символ !:

{#if !isRed}
  <p>Not red</p>
{:else}
  <p>Red</p>
{/if}

Всередині else ви можете перевірити додаткову умову. Тут на допомогу приходить вираз {:else if somethingElse}:

{#if isRed}
  <p>Red</p>
{:else if isGreen}
  <p>Green</p>
{:else}
  <p>Not red nor green</p>
{/if}

Ви можете мати більше ніж один подібний блок, допускається і вкладеність. Ось складніший приклад:

{#if isRed}
  <p>Red</p>
{:else if isGreen}
  <p>Green</p>
{:else if isBlue}
  <p>It is blue</p>
{:else}
  {#if isDog}
    <p>It is a dog</p>
  {/if}
{/if}

Циклічні конструкції в шаблонах Svelte

В шаблонах Svelte ви можете використовувати цикли з конструкціями {#each}{/each}:

<script>
let goodDogs = ['Roger', 'Syd']
</script>

{#each goodDogs as goodDog}
  <li>{goodDog}</li>
{/each}

Якщо ви мали досвід з іншими фреймворками, що використовують шаблони, синтаксис дуже схожий.

Щоб отримати індекс кожної ітерації:

<script>
let goodDogs = ['Roger', 'Syd']
</script>

{#each goodDogs as goodDog, index}
  <li>{index}: {goodDog}</li>
{/each}

При динамічному редагуванні списку, видаленні та додаванні елементів, необхідно завжди передавати ідентифікатор .

Розглянемо приклад:

<script>
let goodDogs = ['Roger', 'Syd']
</script>

{#each goodDogs as goodDog (goodDog)}
  <li>{goodDog}</li>
{/each}

<!-- з індексом -->
{#each goodDogs as goodDog, index (goodDog)}
  <li>{goodDog}</li>
{/each}

Ви також можете передати об'єкт, але якщо ваш список має унікальний ідентифікатор для кожного елемента, краще використовувати його:

<script>
let goodDogs = [
  { id: 1, name: 'Roger'},
  { id: 2, name: 'Syd'}
]
</script>

{#each goodDogs as goodDog (goodDog.id)}
  <li>{goodDog.name}</li>
{/each}

<!-- з використанням index -->
{#each goodDogs as goodDog, index (goodDog.id)}
  <li>{goodDog.name}</li>
{/each}

Проміси в шаблонах Svelte

Проміси — чудовий інструмент для роботи з асинхронними подіями в JavaScript. А з введенням в ES2017 синтаксису await, використовувати проміси стало ще легше.

Svelte пропонує конструкцію {#await} в шаблонах для прямої роботи з промісами на рівні шаблону.

Ми можемо почекати, коли проміс перейде в статус resolved, та визначити інший UI для різних станів промісу: unresolved, resolved та rejected.

Розглянемо, як усе працює. Ми оголошуємо проміс та використовуємо блок {#await}, де ми чекаємо на його результат.

Як тільки проміс перейде в стан resolved, результат передається в блок {:then}:

<script>
  const fetchImage = (async () => {
    const response = await fetch('https://web.archive.org/web/20230325134733/https://dog.ceo/api/breeds/image/random')
    return await response.json()
  })()
</script>

{#await fetchImage}
  <p>...waiting</p>
{:then data}
  <img src={data.message} alt="Dog image" />
{/await}

Ви можете відловити стан rejected проміса у {:catch} блоці:

{#await fetchImage}
  <p>...waiting</p>
{:then data}
  <img src={data.message} alt="Dog image" />
{:catch error}
  <p>An error occurred!</p>
{/await}

З прикладом можна ознайомитись за посиланням.

Робота з подіями у Svelte

Відстеження DOM-подій

У Svelte ви можете визначити слухача для подій DOM одразу в шаблоні, через синтаксис on:<event>.

Наприклад, щоб відстежити подію click, необхідно передати функцію в атрибут on:click. Щоб відстежити подію onmousemove, ми передаємо функцію в атрибут on:mousemove відповідно.

Розглянемо приклад з функцією-обробником, визначеною в атрибуті:

<button on:click={() => {
  alert('clicked')
}}>Click me</button>

Та інший приклад з функцією-обробником в секції script компонента:

<script>
const doSomething = () => {
  alert('clicked')
}
</script>

<button on:click={doSomething}>Click me</button>

Вбудована функція більше підходить для випадків, коли в обробнику мало коду. В іншому випадку краще звернутись до <script>.

Svelte передає обробник події як аргумент функції, а це зручно, щоб зупинити виплиття (propagation), або щоб створити посилання на властивості об'єкта Event:

<script>
const doSomething = event => {
  console.log(event)
  alert('clicked')
}
</script>

Вище ми згадали про «припинення виплиття». Це часто потрібно, наприклад, щоб зупинити відправлення форми. Svelte пропонує модифікатори, щоб не робити це вручну. stopPropagation та preventDefault застосовуються найчастіше.

Для модифікаторів існує такий синтаксис: <button on:click|stopPropagation|preventDefault={doSomething}>Click me</button>.

Бувають й інші модифікатори, однак вони використовуються рідше. capture зумовлює події захвату, замість спливання, once запускає подію одноразово, self запускає подію лише якщо цільовий елемент події — поточний об'єкт (видалення його з ієрархії захвату/виплиття).

Створення власних подій в компонентах

Ми можемо створювати також власні події в компонентах та використовувати такий самий синтаксис, як і у вбудованих DOM-подіях. Для цього нам треба імпортувати функцію createEventDispatcher з пакета svelte та викликати її, щоб створити диспетчер подій:

<script>
  import { createEventDispatcher } from 'svelte'
  const dispatch = createEventDispatcher()
</script>

Після цього ми можемо викликати dispatch(), передати рядок, який ідентифікує подію (використовуватимемо його в конструкції on: в компонентах):

<script>
  import { createEventDispatcher } from 'svelte'
  const dispatch = createEventDispatcher()

  //коли час запустити подію
  dispatch('eventName')
</script>

Тепер інші компоненти можуть використовувати наші рядки:

<ComponentName on:eventName={event => {
  //певна логіка
}} />

Ми також можемо передати об'єкт як другий параметр dispatch():

<script>
  import { createEventDispatcher } from 'svelte'
  const dispatch = createEventDispatcher()
  const value = 'something'

  //коли час виконати подію
  dispatch('eventName', value)

  //або

  dispatch('eventName', {
    someProperty: value
  })
</script>

Об'єкт, переданий в dispatch(), буде доступним в event.

Що далі?

Якщо вас зацікавив фреймворк і ви б хотіли б дізнатись більше, ознаймтесь з:

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.9K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація