Build high-performance web applications with SvelteJS - a lightweight JavaScript compiler
Author: Maximilian Schwarzmüller
<script defer src="...">
- scripts with the defer
attribute will prevent the DOMContentLoaded event from firing until the script has loaded and finished evaluating.npx degit sveltejs/template my-svelte-project
# or download and extract
cd my-svelte-project
npm install
npm run dev
Пропсы можно задавать в main.js
const app = new App({
target: document.body,
props: {
name: 'world',
age: 45
}
});
их нужно явно обозначить экспортами в компоненте вот так:
<script>
export let name;
export let age;
</script>
Компонент App.svelte
<main>
<h1>Hello {name}, my age is {age}!</h1>
<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
<button on:click={incrementAge}>Increment Age</button>
</main>
<main>
соотв. файл main.json:click={eventHandler}
Label statement in JS, обычно не применяется. Задуман в качестве метки для инструкции перехода. Может применяться в циклах, и т.п.
В Svelte используется метка $ для обозначения кода, который будет выполняться каждый раз, при обновлении
<script>
let name = 'Slava';
$: uppercaseName = name.toUpperCase();
function changeName() {
name = 'Maximilian';
}
</script>
<h1>Hello {uppercaseName}!</h1>
<button on:click={changeName}>Change name</button>
<script>
let name = 'Slava';
let age = 37;
$: uppercaseName = name.toUpperCase();
$: if (name === 'Maximilian) {
age = 31;
}
function changeName() {
name = 'Maximilian';
}
</script>
<h1>Hello {uppercaseName}!</h1>
<h2>Your age is {age}</h2>
<button on:click={changeName}>Change name</button>
<script>
let name = 'Slava';
function inputName(event) {
name = event.target.value;
}
</script>
<h1>Hello {name}!</h1>
<input type="text" value={name} on:input={inputName}>
<script>
let name = 'Slava';
</script>
<h1>Hello {name}!</h1>
<input type="text" bind:value={name}>
<-- src/MyComponent.svelte -->
<h2>This is my component</h2>
<-- src/App.svelte -->
<script>
import MyComponent from './MyComponent';
</script>
<h1>Hello</h1>
<MyComponent />
Custom entry points at index.html
file and bind it in main.js
// public/index.html
<!-- Some code before -->
<body>
<div id="root"></div>
</body>
</html>
// src/main.js
const app = new App({
target: document.getElementById('root'), // document.body
props: {
name: 'world',
age: 45
}
});
export default app;
export
в Svelte означает, что переменную можно изменять извне компонента<-- src/MyComponent.svelte -->
<script>
export let text = 'component';
</script>
<h2>This is my {text}</h2>
<-- src/App.svelte -->
<script>
import MyComponent from './MyComponent';
</script>
<h1>Hello</h1>
<MyComponent text='Compontent'/>
<script>
let name = 'Slava';
let url = 'https://some.image.url';
</script>
<MyComponent {name} imageUrl={url}/>
<script>
let text = 'some <strong>text</strong> is bold';
</script>
<p>{text}</p>
<p>{@html text}</p>
data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" onload="alert('You were hacked!');
, alert is showed<script>
let userInput = '';
</script>
<input type="text" bind:value={userInput} />
<div>{@html userInput}</div>
<div class={userImage ? 'thumb' : 'thumb thumb-placeholder'}>
<!-- Alternative way - using directive -->
<div class="thumb" class:thumb-placeholder={!userName}>
{#if formState === 'done'}
<ContactCard userName={name} jobTitle={title} {description} userImage={image} />
{:else if formState === 'invalid'}
<p>Invalid input.</p>
{/if}
let contacts = [];
// later
contacts = [...contacts, newContact]
{#each createdContacts as contact}
<ContactCard
userName={contact.userName}
jobTitle={contact.jobTitle}
description={contact.description}
userImage={contact.imageUrl} />
{/each}
{:else}
в списках отображает контент, когда список пуст{#each contacts as contact, index}
- вторым аргументом передается индекс элемента{#each createdContacts as contact, index}
<h2># {index + 1}</h2>
<ContactCard
userName={contact.userName}
jobTitle={contact.jobTitle}
description={contact.description}
userImage={contact.imageUrl} />
{:else}
<p>No any contact found.</p>
{/each}
{#each contacts as contact, index (contact.id)}
<h2># {index + 1}</h2>
<ContactCard />
{:else}
<p>No any contact found.</p>
{/each}
{:else}
on:click|once
- срабатывает только один раз, удаляя затем обработчик.on:scroll|passive
- улучшает производительность прокрутки при тач-событиях или при прокрутке колёсиком мышкиon:click|capture
- вызывает событие в режиме capture вместо bubbling.on:click|stopPropagation
- предотвращает "всплытие" событияon:click|preventDefault
- вызывает event.preventDefault() перед запуском обработчика.Можно назначить несколько обработчиков для одного события:
<button on:click={increment} on:click={track}>Нажми меня!</button>
// Main.js
import App from './App.svelte';
import Header from './UI/Header.svelte';
const app = new App({
target: document.querySelector('#app')
});
const app = new App({
target: document.querySelector('#header')
});
export default app;
<App>, <Products>, <Checkout>
<Modal>, <Card>, <Button>
bind:value
синтаксис не будет работать для свойств, которые экспортированы наружу.<input ... on:input>
- это называется "event forwarding".// Product.svelte
<script>
import { createEventDispatcher } from 'svelte';
export let productTitle;
const dispatch = createEventDispatcher();
const addToCart = () => dispatch('add-to-cart');
const deleteProduct = () => dispatch('delete', { id: 'p1' });
</script>
<article>
<h1>{productTitle}</h1>
<button on:click={addToCart}>Add to Cart</button>
<button on:click={deleteProduct}>Delete</button>
</article>
// App.svelte
<Product
productTitle="A Book"
on:add-to-cart={() => alert('Added to cart!')}
on:delete={(event) => alert(`Product ${event.detail.id} deleted!`)}
/>
dispatch(<message>, <payload>)
event.detail <payload>
Custom Event
<Product {...product}/>
export let bestseller = false;
Слоты позволяют указать где разместить вложенные компоненты
// Box.svelte
<div class="box">
<slot /> // Вложенные компоненты отобразятся здесь
</div>
// App.svelte
<Box>
<h2>Привет!</h2>
<p>Это компонент box. Тут можно разместить что угодно.</p>
</Box>
// Modal.svelte
<div class="modal">
<header>
<slot name="header" /> // Тут отобразится <h1>Hello!</h1>
</header>
<div class="content">
<slot /> // Тут отобразится <p>This works!</p>
</div>
<footer>
<slot name="footer">
<button>Close</button> // Эта кнопка будет замещена кнопкой Confirm
</slot>
</footer>
</div>
// App.svelte
<Modal>
<h1 slot="header">Hello!</h1>
<p>This works!</p>
<button slot="footer">Confirm</button>
</Modal>
Есть возможность передать значение из компонента в родительском компонент - обратно направленный поток данных. Сделать это можно используя свойства slot
<script>
let hovering = false;
</script>
<div on:mouseenter={() => hovering = true} on:mouseleave={() => hovering = false}>
<slot hovering={hovering}></slot>
</div>
let:
<script>
let hovering = false;
</script>
<Hoverable let:hovering={hovering}>
<div class:active={hovering}>
{#if hovering}
<p>На меня навели.</p>
{:else}
<p>Наведи на меня!</p>
{/if}
</div>
</Hoverable>
hovering
в обоих компонентах, т.к. хотя они связаны директивой, однако они все же две отдельные сущности.Creation:
<script>
и выполняется базовая инициализация:Updates:
// Restore selection
tick().then(() => {
e.target.selectionStart = selectionStart;
e.target.selectionEnd = selectionEnd;
});
Прокрутка
const modal = document.querySelector('.modal');
modal.scrollTo(0, modal.scrollHeight);
Какая клавиша нажата и позиция начала и окончания выделения текста.
const keyPressed = (e) {
if (e.which !== 9) { // Ignore, if not TAB pressed
return;
}
e.preventDefault();
const start = e.targer.selectionStart;
const end = e.targer.selectionEnd;
// ...
}
slot
. Например мою кнопку пришлось обернуть в <div>
<input type={type} bind:value={val} />
- на данный момент динамическое свойство type
не работает одновременно с двухсторонним связыванием.<input type="number">
нужно помнить что содержимое event.target.value
это текст. Однако при использовании директивы bind:value
Svelte уже обрабатывает значения как числа.<input type="checkbox" bind:checked={agreed}>
checked
bind:group
<select>
Dropdowns<select bind:value={favCar}>
<option value="aston-martin">
Aston Martin
</option>
<option value="ferrari">
Ferrari
</option>
<option value="porsche">
Porsche
</option>
</select>
Доступная только для чтения привязка this применяется к любому HTML-элементу (или компоненту) и позволяет вам получить ссылку на отрендеренный элемент.
bind:this={usernameInput}
// CustomInput.svelte
<script>
export let val;
export function empty() {
val = '';
};
</script>
<input type="text" bind:value={val} />
// App.svelte
<script>
let val = 'Max';
let customInput;
const clearCustomInput = () => customInput.empty();
</script>
<CustomInput bind:val={val} bind:this={customInput}/>
<button on:click={clearCustomInput}>Clear Inputs</button>
Svelte не имеет встроенных средств для валидации, и предоставляет возможность реализовать свое решение, или использовать готовые библиотеки, например validate.js
$:
не нужно объявлять переменные. Кстати они по другому называются "реактивные объявления".Проблема: С ростом сложности приложения, увеличивается количество statefull компонентов, которые должны быть связанны между собой. Ответственность за изменения этих данных распределена между ними, это удобно, но управлять изменениями становится сложно. Svelte.js имеет решение для таких случаев.
writable()
для создания хранилищаsubscribe()
позволяет подписаться на изменения, используется для чтения/вывода данныхset()
или update()
которая изменяет данные, может использоваться в обработчиках событийset
полностью заменяет хранилище новыми даннымиupdate
принимает функцию// cartStore.js
import { writable } from 'svelte/store';
const cart = writable([
{
id: "p1",
title: "Test",
price: 9.99
},
{
id: "p2",
title: "Test",
price: 9.99
}
]);
export default cart;
// Cart.svelte
<script>
import CartItem from "./CartItem.svelte";
import cartStore from "./cartStore.js"
let items;
cartStore.subscribe(
data => items = data
);
</script>
<ul>
{#each items as item (item.id)}
<CartItem id={item.id} title={item.title} price={item.price} />
{:else}
<p>No items in cart yet!</p>
{/each}
</ul>
// Product.svelte
<script>
import Button from "../UI/Button.svelte";
import cartStore from "../Cart/cartStore.js"
export let id;
export let title;
export let price;
function addToCart() {
cartStore.update(
items => [...items, { id, title, price }]
);
}
</script>
<div class="product">
<h1>{title}</h1>
<h2>{price}</h2>
<Button on:click={addToCart}>Add to Cart</Button>
</div>
subscribe()
возвращает функцию, с помощью которой можно отменить подписку на обновления в хранилище. Это удобно сделать в хуке onDestroy
// Cart.svelte
<script>
import { onDestroy } from 'svelte';
import cartStore from "./cartStore.js"
let items;
const unsubscribe = cartStore.subscribe(
data => items = data
);
onDestroy(() => {
if (unsubscribe) {
unsubscribe();
}
});
</script>
$
перед именем хранилища позволяет сразу получать значения из хранилища, а Svelte управлять подпиской автоматически.<script>
import { onDestroy } from 'svelte';
import CartItem from "./CartItem.svelte";
import cartItems from "./cartStore.js"
</script>
<ul>
{#each $cartItems as item (item.id)}
<CartItem id={item.id} title={item.title} price={item.price} />
{:else}
<p>No items in cart yet!</p>
{/each}
</ul>
Иногда нам не нужно, чтобы подписчики могли изменять данные.
import { readable } from 'svelte/store';
let currentDate = new Date();
export const time = readable(currentDate, function start(set) {
const interval = setInterval(() => {
currentDate = new date();
set(currentDate);
}, 1000);
return function stop() {
clearInterval(interval);
};
});
Можно создать хранилище с собственной логикой по управлению состоянием для конкретных задач.
import { writable } from 'svelte/store';
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const count = createCount();
subscribe
.update/set
С помощью derived можно создать хранилище, значение которого вычисляется на основе значения одного или нескольких других хранилищ + некоторая логика.
import { derived } from 'svelte/store';
import time from './timeStore';
export const elapsed = derived(
time,
$time => Math.round(($time - start) / 1000)
);
Если у вас есть записываемое хранилище — то есть у него есть метод set — можно сделать привязку к его значению тем же способом, что и привязку к обычной переменной в компоненте.
```JS
<button on:click={() => $name += '!'}> Add exclamation mark!
$name += '!'
эквивалентно записи name.set($name + '!')
.