Skip to content

Latest commit

 

History

History
390 lines (281 loc) · 19.3 KB

02-component-format.md

File metadata and controls

390 lines (281 loc) · 19.3 KB

Формат компонента



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

Все три части — script, style и разметка — не являются обязательными.

<script>
	// логика описывается здесь
</script>

<!-- разметка (0 или более элементов) помещается здесь -->

<style>
	/* стили должны быть здесь */
</style>

<script>

Блок <script> содержит JavaScript, который запускается при создании экземпляра компонента. Переменные, объявленные (или импортированные) в этом блоке, 'видны' из разметки компонента. Есть ещё четыре дополнительных правила:

1. export объявляет свойство компонента


Svelte использует ключевое слово export, чтобы пометить объявление переменной как свойство, что означает, что оно становится доступным извне всему, что будет использовать этот компонент (см. раздел Атрибуты и свойства для дополнительной информации).

<script>
	export let foo;

	// Значения переменных, объявленных как свойства,
	// доступны сразу же
	console.log({ foo });
</script>

Для свойства можно указать начальное значение по умолчанию, которое будет использоваться, в случае когда при инициализации экземпляра компонента потребитель компонента не задаст это свойство(или задаст ему значение undefined). Учитывайте, что если потребитель позднее уберёт значение для свойства, то его значение станет равным undefined, а не начальному.

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

<script>
	export let bar = 'необязательное начальное значение по умолчанию';
	export let baz = undefined;
</script>

При экспорте свойств заданных ключевыми словами const, class или function, снаружи, вне компонента они будут доступны только для чтения. В то же время, функции являются обычными свойствами (prop), как показано ниже.

<script>
	// доступны только для чтения
	export const thisIs = 'readonly';

	export function greet(name) {
		alert(`hello ${name}!`);
	}

	// обычное свойство
	export let format = n => n.toFixed(2);
</script>

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


Есть возможность использовать зарезервированные слова в качестве имен свойств.

<script>
	let className;

	// создает свойство с именем  `class`, 
	// которое является зарезервированным словом в JS
	export { className as class };
</script>

2. Присваивания 'реактивны'


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

Присваивания с обновлением (count += 1) и присваивания свойствам (obj.x = y) будут иметь тот же эффект.

<script>
	let count = 0;

	function handleClick () {
		// вызов этой функции приведёт к обновлению компонента,
		// если в разметке есть ссылка на `count`
		count = count + 1;
	}
</script>

Поскольку реактивность Svelte основана на присваиваниях, использование методов массива, таких как .push() и .splice(), не будет автоматически инициировать обновления. Для запуска обновления требуется последующее присваивание. Это и более подробную информацию также можно найти в учебнике.

 <script>
 	let arr = [0, 1];

 	function handleClick () {
 		// this method call does not trigger an update
 		arr.push(2);
 		// this assignment will trigger an update
 		// if the markup references `arr`
 		arr = arr
 	}
 </script>

Блоки <script> в Svelte выполняются только при создании компонента, поэтому присвоения в блоке <script> не выполняются автоматически при обновлении свойства (prop). Если вы хотите отслеживать изменения в свойстве, смотрите следующий пример в следующем разделе.

<script>
	export let person;
	// это выражение установит `name` только при создании компонента
	// и не будет его обновлять, когда это сделает `person`.
	let { name } = person;
</script>

3. $: делает выражение реактивным


Любое выражение на верхнем уровне (то есть, не внутри блока или функции) можно сделать реактивным, добавив перед ним JS метку $:. Реактивные выражения запускаются после другого кода скрипта и перед отрисовкой разметки компонента, когда изменяются значения переменных, которые в него входят.

<script>
	export let title;
	export let person;

	// это выражение будет обновлять `document.title` каждый раз,
	// когда свойство `title` изменится
	$: document.title = title;

	$: {
		console.log(`можно объединить в блок несколько выражений`);
		console.log(`текущий заголовок: ${title}`);
	}

	// это выражение обновит `name` когда 'person' изменится
	$: ({ name } = person);

	// не делайте так. Это выражение выполнится перед предыдущей строкой
	let name2 = name;
</script>

Зависимостями реактивного выражения становятся только те переменные, которые явно указаны внутри блока $:. Например, в коде ниже значение total, будет обновляться только, когда изменится x, но не y.

<script>
	let x = 0;
	let y = 0;
	
	function yPlusAValue(value) {
		return value + y;
	}
	
	$: total = yPlusAValue(x);
</script>

Всего: {total}
<button on:click={() => x++}>
	Добавить X
</button>

<button on:click={() => y++}>
	Добавить Y
</button>

Важно отметить, что реактивные блоки упорядочиваются с помощью простого статического анализа во время компиляции, и компилятор смотрит только на переменные, которые присваиваются и используются в самом блоке, а не в вызываемых им функциях. Это означает, что yDependent не будет обновляться при обновлении x в следующем примере:

<script>
	let x = 0;
	let y = 0;
	
	const setY = (value) => {
		y = value;
	}
	
	$: yDependent = y;
	$: setY(x);
</script>

Перемещение строки $: yDependent = y ниже $: setY(x) приведет к тому, что yDependent будет обновляться при обновлении x.


Если выражением является только присваивание значения ранее не объявленной переменной, Svelte самостоятельно объявит такую переменную через оператор let.

<script>
	export let num;

	// нет необходимости объявлять `squared` и `cubed`,
	// Svelte сделает это за нас
	$: squared = num * num;
	$: cubed = squared * num;
</script>

4. Добавьте префикс $ к хранилищу для получения его значения


Хранилищем является любой объект, который допускает реактивный доступ к своему значению посредством так называемого контракта хранилища.

Модуль svelte/store содержит минимальную реализацию хранилищ, соответствующих такому контракту.

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

Присваивание значений переменным с префиксом $ требует, чтобы эти переменные являлись записываемыми хранилищами, и приведёт к вызову метода хранилища .set.

Обратите внимание, что хранилище должно быть объявлено на верхнем уровне компонента, а не, например, внутри функции или блока if.

Локальные переменные (которые не являются ссылкой на хранилище) не должны иметь префикс $.

<script>
	import { writable } from 'svelte/store';

	const count = writable(0);
	console.log($count); // выведет 0

	count.set(1);
	console.log($count); // выведет 1

	$count = 2;
	console.log($count); // выведет 2
</script>

Контракт хранилища

store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }

Вы можете создавать собственные варианты хранилищ, без участия svelte/store, реализовав контракт хранилища:

  1. Хранилище обязано иметь метод .subscribe, который должен принимать в качестве аргумента функцию подписки. При вызове этого метода, сразу же синхронно должна быть вызвана функция подписки, с передачей ей текущего значения хранилища. Далее все полученные функции подписки должны синхронно вызываться при любом изменении значения хранилища.
  2. Метод .subscribe должен возвращать функцию отмены подписки. Вызов функции отмены должен привезти к тому, что соответствующая функция подписки более не должна вызываться хранилищем.
  3. По желанию, хранилище может иметь метод .set, который должен принимать параметром новое значение для хранилища и синхронно вызывать все активные функции подписки. Такое хранилище называется записываемым хранилищем.

Для обеспечения совместимости с RxJS Observables метод .subscribe может возвращать объект с методом .unsubscribe, вместо непосредственно функции отписки. Но если метод .subscribe вызывает подписку асинхронно(спецификация Observable это допускает), Svelte будет видеть значение хранилища как undefined, пока вызов не завершится.

<script context="module">


Блок <script> с атрибутом context="module" выполняется только один раз при первичной обработке модуля, а не при каждой инициализации экземпляров компонента. Значения, объявленные в этом блоке, доступны в разметке компонента и в обычном блоке <script>(но не наоборот).

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

Вы не сможете сделать export default, поскольку экспортом по умолчанию является сам компонент.

Переменные, объявленные в блоке context="module", не являются реактивными, поэтому присваивание им новых значений не будет приводить к перерисовке компонента, хоть сами переменные и обновятся. Для значений, которые предполагается использовать в нескольких компонентах, лучше использовать хранилища.

<script context="module">
	let totalComponents = 0;

	// ключевое слово export позволяет импортировать эту функцию, например.
	// `import Example, { alertTotal } from './Example.svelte'`
	export function alertTotal() {
		alert(totalComponents);
	}
</script>

<script>
	totalComponents += 1;
	console.log(`для этого компонента было создано ${totalComponents} экземпляр(ов)`);
</script>

<style>


CSS стили внутри блока <style> будут изолированы внутри данного компонента.

Это достигается путём добавления класса ко всем затронутым элементам, имя которого получено хэш-функцией из стилей компонента (например, svelte-123xyz).

<style>
	p {
		/* это затронет только элемент <p> в этом компоненте */
		color: burlywood;
	}
</style>

Для применения стиля к селектору глобально, используйте модификатор :global(...).

<style>
	:global(body) {
		/* этот стиль для <body> */
		margin: 0;
	}

	div :global(strong) {
		/* этот стиль будет применяться ко всем элементам <strong> 
		   в любом компоненте, который находится внутри 
	       элементов <div> данного компонента */
		color: goldenrod;
	}

	p:global(.red) {
	/* Это будет применяться ко всем <p> элементам, 
		принадлежащим этому компоненту с классом `red`, 
		даже если class="red" изначально не появляется в разметке, 
		а вместо этого добавлен во время выполнения. 
		Это полезно, когда класс элемента динамически применяется, 
		например, при непосредственном обновлении свойства classList элемента. */
	}
</style>

Если требуется сделать глобальной анимацию @keyframes, добавьте к имени анимации префикс -global-.

Часть имени -global- будет удалена при компиляции, и вы сможете обратиться к анимации просто по имени my-animation-name в любом месте вашего кода.

<style>
	@keyframes -global-my-animation-name {...}
</style>

На компонент должен быть только 1 тег верхнего уровня <style>.

Тем не менее, тег <style> может быть вложен в другие элементы или логические блоки.

В этом случае тег <style> будет вставлен как есть в DOM, раздел или обработка тега <style> не будет выполнена.

<div>
 	<style>
 		/* Этот тег стиля будет вставлен как есть */
 		div {
 			/* Это будет применяться ко всем элементам `<div>` в DOM */
 			color: red;
 		}
 	</style>
 </div>