1. Введение
Что такое горит
Lit — это простая библиотека для создания быстрых и легких веб-компонентов, которые работают в любой среде или вообще без нее. С Lit вы можете создавать общие компоненты, приложения, системы проектирования и многое другое.
Что вы узнаете
Как перевести на Lit несколько концепций React, таких как:
- JSX и шаблоны
- Компоненты и реквизит
- Состояние и жизненный цикл
- Крючки
- Дети
- Ссылки
- Посредническое государство
Что ты построишь
В конце этой лаборатории вы сможете преобразовать концепции компонентов React в их аналоги в Lit.
Что вам понадобится
- Последняя версия Chrome, Safari, Firefox или Edge.
- Знание HTML, CSS, JavaScript и Chrome DevTools .
- Знание Реакта
- (Дополнительно) Если вам нужен лучший опыт разработки, скачайте VS Code . Вам также понадобится плагинlit-plugin для VS Code и NPM .
2. Лит против Реакция
Основные концепции и возможности Lit во многом схожи с React, но у Lit также есть некоторые ключевые отличия и особенности:
Это маленький
Lit крошечный: он составляет около 5 КБ, минифицированный и заархивированный по сравнению с 40+ КБ React + ReactDOM .
Это быстро
В общедоступных тестах , сравнивающих систему шаблонов Lit,lit-html, с VDOM React,lit-html работает на 8-10% быстрее, чем React в худшем случае, и на 50%+ быстрее в более распространенных случаях использования.
LitElement (базовый класс компонента Lit) добавляет минимальные накладные расходы кlit-html, но превосходит производительность React на 16-30% при сравнении функций компонента, таких как использование памяти, взаимодействие и время запуска.
Не требует сборки
Благодаря новым функциям браузера, таким как модули ES и литералы шаблонов с тегами, Lit не требует компиляции для запуска . Это означает, что среды разработки можно настроить с помощью тега сценария + браузера + сервера, и все готово.
Благодаря модулям ES и современным CDN, таким как Skypack или UNPKG , вам может даже не понадобиться NPM для начала работы!
Хотя при желании вы все равно можете собрать и оптимизировать Lit-код . Недавняя консолидация разработчиков вокруг собственных ES-модулей пошла на пользу Lit: Lit — это обычный JavaScript, и нет необходимости в специфичных для платформы CLI или обработке сборок .
Независимость от фреймворка
Компоненты Lit основаны на наборе веб-стандартов, называемых веб-компонентами. Это означает, что создание компонента в Lit будет работать в текущих и будущих средах . Если он поддерживает элементы HTML, он поддерживает веб-компоненты.
Единственные проблемы с взаимодействием фреймворков возникают тогда, когда фреймворки имеют ограниченную поддержку DOM. React — один из таких фреймворков, но он позволяет использовать аварийные люки через ссылки, а ссылки в React — не лучший опыт для разработчиков.
Команда Lit работает над экспериментальным проектом под названием @lit-labs/react
который будет автоматически анализировать ваши компоненты Lit и генерировать оболочку React, чтобы вам не приходилось использовать ссылки.
Кроме того, Custom Elements Everywhere покажет вам, какие фреймворки и библиотеки хорошо работают с пользовательскими элементами!
Первоклассная поддержка TypeScript
Хотя весь код Lit можно написать на JavaScript, Lit написан на TypeScript , и команда Lit рекомендует разработчикам также использовать TypeScript!
Команда Lit работает с сообществом Lit, чтобы помочь поддерживать проекты, которые обеспечивают проверку типов TypeScript и Intellisense в шаблонах Lit как на этапе разработки, так и на этапе сборки с помощьюlit lit-analyzer
lit-plugin
.
Инструменты разработки встроены в браузер
Освещенные компоненты — это просто элементы HTML в DOM . Это означает, что для проверки ваших компонентов вам не нужно устанавливать какие-либо инструменты или расширения для вашего браузера.
Вы можете просто открыть инструменты разработки, выбрать элемент и изучить его свойства или состояние.
Он создан с учетом рендеринга на стороне сервера (SSR).
Lit 2 был создан с учетом поддержки SSR. На момент написания этой кодовой лаборатории команда Lit еще не выпустила инструменты SSR в стабильной форме, но команда Lit уже развернула компоненты, отображаемые на стороне сервера, в продуктах Google и протестировала SSR в приложениях React. Команда Lit планирует вскоре выпустить эти инструменты на GitHub.
А пока вы можете следить за прогрессом команды Lit здесь .
Это низкий бай-ин
Лит не требует значительных усилий в использовании! Вы можете создавать компоненты в Lit и добавлять их в существующий проект. Если они вам не нравятся, вам не нужно конвертировать все приложение сразу, поскольку веб-компоненты работают и в других фреймворках!
Вы создали целое приложение в Lit и хотите перейти на что-то другое? Что ж, тогда вы можете разместить свое текущее приложение Lit внутри новой среды и перенести все, что захотите, в компоненты новой среды.
Кроме того, многие современные платформы поддерживают вывод в веб-компонентах , а это означает, что они обычно могут помещаться внутри элемента Lit.
3. Подготовка и исследование игровой площадки.
Есть два способа сделать эту кодовую лабораторию:
- Вы можете сделать это полностью онлайн, в браузере.
- (Дополнительно) Вы можете сделать это на своем локальном компьютере с помощью VS Code.
Доступ к коду
На протяжении всей кодовой лаборатории будут ссылки на игровую площадку Lit, например:
Игровая площадка — это песочница кода, которая полностью работает в вашем браузере. Он может компилировать и запускать файлы TypeScript и JavaScript, а также автоматически разрешать импорт в модули узлов. например
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
Вы можете пройти весь урок на игровой площадке Lit, используя эти контрольные точки в качестве отправных точек. Если вы используете VS Code, вы можете использовать эти контрольные точки для загрузки стартового кода для любого шага, а также для проверки своей работы.
Изучение пользовательского интерфейса освещенной игровой площадки
На снимке экрана пользовательского интерфейса освещенной игровой площадки выделены разделы, которые вы будете использовать в этой лаборатории кода.
- Селектор файлов. Обратите внимание на кнопку плюс...
- Редактор файлов.
- Предварительный просмотр кода.
- Кнопка перезагрузки.
- Кнопка загрузки.
Настройка VS Code (расширенная)
Вот преимущества использования этой настройки VS Code:
- Проверка типа шаблона
- IntelliSense и автозаполнение шаблонов
Если у вас уже установлен NPM, VS Code (с плагиномlit-plugin ) и вы знаете, как использовать эту среду, вы можете просто загрузить и запустить эти проекты, выполнив следующие действия:
- Нажмите кнопку загрузки
- Извлеките содержимое файла tar в каталог.
- (Если TS) настройте быстрый tsconfig , который выводит модули es и es2015+.
- Установите сервер разработки, который может разрешать спецификаторы пустых модулей (команда Lit рекомендует @web/dev-server ).
- Вот пример
package.json
- Вот пример
- Запустите сервер разработки и откройте браузер (если вы используете @web/dev-server, вы можете использовать
npx web-dev-server --node-resolve --watch --open
)- Если вы используете пример
package.json
используйтеnpm run dev
- Если вы используете пример
4. JSX и шаблоны
В этом разделе вы познакомитесь с основами шаблонизации в Лит.
JSX и шаблоны Lit
JSX — это синтаксическое расширение JavaScript, которое позволяет пользователям React легко писать шаблоны в своем коде JavaScript. Освещенные шаблоны служат той же цели: выражают пользовательский интерфейс компонента как функцию его состояния.
Основной синтаксис
В React вы можете визуализировать приветственный мир JSX следующим образом:
import 'react';
import ReactDOM from 'react-dom';
const name = 'Josh Perez';
const element = (
<>
<h1>Hello, {name}</h1>
<div>How are you?</div>
</>
);
ReactDOM.render(
element,
mountNode
);
В приведенном выше примере есть два элемента и включенная переменная «имя». В Lit вы должны сделать следующее:
import {html, render} from 'lit';
const name = 'Josh Perez';
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Обратите внимание, что шаблонам Lit не требуется фрагмент React для группировки нескольких элементов в своих шаблонах.
В Lit шаблоны обернуты шаблоном LIT eral с тегом html
, от которого Lit и получил свое название!
Значения шаблона
Шаблоны Lit могут принимать другие шаблоны Lit, известные как TemplateResult
. Например, оберните name
тегами курсивом ( <i>
) и оберните его теговым литералом шаблона . Примечание. Обязательно используйте символ обратной галочки ( `
), а не символ одинарной кавычки ( '
).
import {html, render} from 'lit';
const name = html`<i>Josh Perez</i>`;
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Lit TemplateResult
может принимать массивы, строки, другие TemplateResult
, а также директивы.
В качестве упражнения попробуйте преобразовать следующий код React в Lit:
const itemsToBuy = [
<li>Bananas</li>,
<li>oranges</li>,
<li>apples</li>,
<li>grapes</li>
];
const element = (
<>
<h1>Things to buy:</h1>
<ol>
{itemsToBuy}
</ol>
</>);
ReactDOM.render(
element,
mountNode
);
Отвечать:
import {html, render} from 'lit';
const itemsToBuy = [
html`<li>Bananas</li>`,
html`<li>oranges</li>`,
html`<li>apples</li>`,
html`<li>grapes</li>`
];
const element = html`
<h1>Things to buy:</h1>
<ol>
${itemsToBuy}
</ol>`;
render(
element,
mountNode
);
Передача и установка реквизита
Одним из самых больших различий между синтаксисами JSX и Lit является синтаксис привязки данных. Например, возьмем этот ввод React с привязками:
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
<input
disabled={disabled}
className={`static-class ${myClass}`}
defaultValue={value}/>;
ReactDOM.render(
element,
mountNode
);
В приведенном выше примере определен вход, который выполняет следующие действия:
- Устанавливает отключенную определенную переменную (в данном случае false)
- Устанавливает класс
static-class
плюс переменную (в данном случае"static-class my-class"
) - Устанавливает значение по умолчанию
В Lit вы должны сделать следующее:
import {html, render} from 'lit';
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
<input
?disabled=${disabled}
class="static-class ${myClass}"
.value=${value}>`;
render(
element,
mountNode
);
В примере Lit добавлена логическая привязка для переключения disabled
атрибута.
Далее идет привязка непосредственно к атрибуту class
, а не className
. К атрибуту class
можно добавить несколько привязок, если только вы не используете директиву classMap
, которая является декларативным помощником для переключения классов.
Наконец, на входе устанавливается свойство value
. В отличие от React, это не сделает элемент ввода доступным только для чтения, поскольку он соответствует встроенной реализации и поведению ввода.
Освещенный синтаксис привязки реквизита
html`<my-element ?attribute-name=${booleanVar}>`;
-
?
префикс — это синтаксис привязки для переключения атрибута элемента. - Эквивалентно
inputRef.toggleAttribute('attribute-name', booleanVar)
- Полезно для элементов, которые используют
disabled
какdisabled="false"
который по-прежнему читается DOM как true, потому чтоinputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
-
.
префикс — это синтаксис привязки для установки свойства элемента - Эквивалентно
inputRef.propertyName = anyVar
- Подходит для передачи сложных данных, таких как объекты, массивы или классы.
html`<my-element attribute-name=${stringVar}>`;
- Привязывается к атрибуту элемента
- Эквивалентно
inputRef.setAttribute('attribute-name', stringVar)
- Подходит для базовых значений, селекторов правил стиля и селекторов запросов.
Передача обработчиков
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
<input
onClick={() => console.log('click')}
onChange={e => console.log(e.target.value)} />;
ReactDOM.render(
element,
mountNode
);
В приведенном выше примере определен вход, который выполняет следующие действия:
- Запишите слово «щелчок» при нажатии на вход
- Зарегистрируйте значение ввода, когда пользователь вводит символ
В Lit вы должны сделать следующее:
import {html, render} from 'lit';
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
<input
@click=${() => console.log('click')}
@input=${e => console.log(e.target.value)}>`;
render(
element,
mountNode
);
В примере с Lit к событию click
добавлен прослушиватель с помощью @click
.
Далее, вместо использования onChange
, существует привязка к собственному событию input
<input>
, поскольку собственное событие change
срабатывает только при blur
(React абстрагируется от этих событий).
Синтаксис обработчика событий Lit
html`<my-element @event-name=${() => {...}}></my-element>`;
- Префикс
@
— это синтаксис привязки для прослушивателя событий. - Эквивалентно
inputRef.addEventListener('event-name', ...)
- Использует собственные имена событий DOM.
5. Компоненты и реквизит
В этом разделе вы узнаете о компонентах и функциях класса Lit. Состояние и хуки более подробно рассматриваются в последующих разделах.
Компоненты класса и LitElement
Эквивалентом компонента класса React в Lit является LitElement, а концепция «реактивных свойств» в Lit представляет собой комбинацию свойств и состояния React. Например:
import React from 'react';
import ReactDOM from 'react-dom';
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {name: ''};
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
В приведенном выше примере есть компонент React, который:
- Отображает
name
- Устанавливает значение
name
по умолчанию в пустую строку (""
) - Меняет
name
на"Elliott"
Вот как это можно сделать в LitElement
В TypeScript:
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
@property({type: String})
name = '';
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
}
В JavaScript:
import {LitElement, html} from 'lit';
class WelcomeBanner extends LitElement {
static get properties() {
return {
name: {type: String}
}
}
constructor() {
super();
this.name = '';
}
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
}
customElements.define('welcome-banner', WelcomeBanner);
И в HTML-файле:
<!-- index.html -->
<head>
<script type="module" src="./index.js"></script>
</head>
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
Обзор того, что происходит в примере выше:
@property({type: String})
name = '';
- Определяет общедоступное реактивное свойство — часть общедоступного API вашего компонента.
- Предоставляет атрибут (по умолчанию), а также свойство вашего компонента.
- Определяет, как преобразовать атрибут компонента (который является строкой) в значение.
static get properties() {
return {
name: {type: String}
}
}
- Он выполняет ту же функцию, что и декоратор
@property
TS, но изначально работает на JavaScript.
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
- Это вызывается всякий раз, когда изменяется какое-либо реактивное свойство.
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Это связывает имя тега элемента HTML с определением класса.
- В соответствии со стандартом пользовательских элементов имя тега должно содержать дефис (-).
-
this
в LitElement относится к экземпляру пользовательского элемента (в данном случае<welcome-banner>
)
customElements.define('welcome-banner', WelcomeBanner);
- Это JavaScript-эквивалент декоратора TS
@customElement
<head>
<script type="module" src="./index.js"></script>
</head>
- Импортирует определение пользовательского элемента.
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- Добавляет пользовательский элемент на страницу
- Устанавливает свойство
name
в'Elliott'
Функциональные компоненты
Lit не имеет 1:1 интерпретации функционального компонента, поскольку не использует JSX или препроцессор. Однако довольно просто составить функцию, которая принимает свойства и отображает DOM на основе этих свойств. Например:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
В Лите это будет:
import {html, render} from 'lit';
function Welcome(props) {
return html`<h1>Hello, ${props.name}</h1>`;
}
render(
Welcome({name: 'Elliott'}),
document.body.querySelector('#root')
);
6. Состояние и жизненный цикл
В этом разделе вы узнаете о состоянии и жизненном цикле Lit.
Состояние
Концепция «Реактивных свойств» Лита представляет собой смесь состояния и свойств React. Реактивные свойства при изменении могут запустить жизненный цикл компонента. Реактивные свойства бывают двух вариантов:
Общественные реактивные свойства
// React
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props)
this.state = {name: 'there'}
}
componentWillReceiveProps(nextProps) {
if (this.props.name !== nextProps.name) {
this.setState({name: nextProps.name})
}
}
}
// Lit (TS)
import {LitElement} from 'lit';
import {property} from 'lit/decorators.js';
class MyEl extends LitElement {
@property() name = 'there';
}
- Определено
@property
- Похожи на реквизиты и состояние React, но изменяемы.
- Публичный API, к которому обращаются и который устанавливают потребители компонента.
Внутреннее реактивное состояние
// React
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props)
this.state = {name: 'there'}
}
}
// Lit (TS)
import {LitElement} from 'lit';
import {state} from 'lit/decorators.js';
class MyEl extends LitElement {
@state() name = 'there';
}
- Определено
@state
- Подобно состоянию React, но изменчиво
- Частное внутреннее состояние, доступ к которому обычно осуществляется изнутри компонента или подклассов.
Жизненный цикл
Жизненный цикл Lit очень похож на жизненный цикл React, но есть некоторые заметные различия.
constructor
// React (js)
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this._privateProp = 'private';
}
}
// Lit (ts)
class MyEl extends LitElement {
@property({type: Number}) counter = 0;
private _privateProp = 'private';
}
// Lit (js)
class MyEl extends LitElement {
static get properties() {
return { counter: {type: Number} }
}
constructor() {
this.counter = 0;
this._privateProp = 'private';
}
}
- Литовой эквивалент также является
constructor
- Супервызову ничего передавать не нужно
- Вызвано (не полностью):
-
document.createElement
-
document.innerHTML
-
new ComponentClass()
- Если на странице присутствует необновленное имя тега, а определение загружено и зарегистрировано с помощью
@customElement
илиcustomElements.define
-
- По функциям похож на
constructor
React.
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- Горящий эквивалент также
render
- Может возвращать любой визуализируемый результат, например
TemplateResult
илиstring
и т. д. - Как и в React,
render()
должен быть чистой функцией. - Будет отображаться в зависимости от того, какой узел возвращает
createRenderRoot()
(по умолчаниюShadowRoot
).
componentDidMount
componentDidMount
похож на комбинацию обратных вызовов жизненного цикла firstUpdated
и connectedCallback
от Lit.
firstUpdated
import Chart from 'chart.js';
// React
componentDidMount() {
this._chart = new Chart(this.chartElRef.current, {...});
}
// Lit
firstUpdated() {
this._chart = new Chart(this.chartEl, {...});
}
- Вызывается при первом отображении шаблона компонента в корне компонента.
- Будет вызываться только в том случае, если элемент подключен, например, не вызывается через
document.createElement('my-component')
до тех пор, пока этот узел не будет добавлен в дерево DOM. - Это хорошее место для настройки компонента, для которого требуется DOM, отображаемый компонентом.
- В отличие от
componentDidMount
ReactDidMount, изменения реактивных свойств вfirstUpdated
вызовут повторный рендеринг, хотя браузер обычно группирует изменения в одном и том же кадре. Если эти изменения не требуют доступа к корневому DOM, их обычно следует вносить вwillUpdate
connectedCallback
// React
componentDidMount() {
this.window.addEventListener('resize', this.boundOnResize);
}
// Lit
connectedCallback() {
super.connectedCallback();
this.window.addEventListener('resize', this.boundOnResize);
}
- Вызывается всякий раз, когда пользовательский элемент вставляется в дерево DOM.
- В отличие от компонентов React, когда пользовательские элементы отсоединяются от DOM, они не уничтожаются и, следовательно, могут быть «подключены» несколько раз.
-
firstUpdated
больше не будет вызываться
-
- Полезно для повторной инициализации DOM или повторного подключения прослушивателей событий, которые были очищены при отключении.
- Примечание.
connectedCallback
может быть вызван доfirstUpdated
, поэтому при первом вызове DOM может быть недоступен.
componentDidUpdate
// React
componentDidUpdate(prevProps) {
if (this.props.title !== prevProps.title) {
this._chart.setTitle(this.props.title);
}
}
// Lit (ts)
updated(prevProps: PropertyValues<this>) {
if (prevProps.has('title')) {
this._chart.setTitle(this.title);
}
}
- Буквальный эквивалент
updated
(с использованием английского прошедшего времени «обновление»). - В отличие от React,
updated
также вызывается при первоначальном рендеринге. - По функциям аналогичен
componentDidUpdate
ReactDidUpdate.
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Горящий эквивалент аналогичен
disconnectedCallback
- В отличие от компонентов React, когда пользовательские элементы отсоединяются от DOM, компонент не уничтожается.
- В отличие от
componentWillUnmount
,disconnectedCallback
вызывается после удаления элемента из дерева. - DOM внутри корня по-прежнему привязан к корневому поддереву.
- Полезно для очистки прослушивателей событий и негерметичных ссылок, чтобы браузер мог собирать мусор в компоненте.
Упражнение
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
В приведенном выше примере есть простые часы, которые делают следующее:
- Он отображает «Hello World! Это так», а затем отображает время.
- Каждую секунду он будет обновлять часы
- При демонтаже очищает интервал вызова галочки
Сначала начнем с объявления класса компонента:
// Lit (TS)
// some imports here are imported in advance
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
}
// Lit (JS)
// `html` is imported in advance
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
}
customElements.define('lit-clock', LitClock);
Затем инициализируйте date
и объявите ее внутренним реактивным свойством с помощью @state
, поскольку пользователи компонента не будут устанавливать date
напрямую.
// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
@state() // declares internal reactive prop
private date = new Date(); // initialization
}
// Lit (JS)
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
static get properties() {
return {
// declares internal reactive prop
date: {state: true}
}
}
constructor() {
super();
// initialization
this.date = new Date();
}
}
customElements.define('lit-clock', LitClock);
Далее визуализируйте шаблон.
// Lit (JS & TS)
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
Теперь реализуем метод галочки.
tick() {
this.date = new Date();
}
Далее идет реализация componentDidMount
. Опять же, аналог Lit представляет собой смесь firstUpdated
и connectedCallback
. В случае с этим компонентом tick
с помощью setInterval
не требует доступа к DOM внутри корня. Кроме того, интервал очищается при удалении элемента из дерева документа, поэтому в случае его повторного подключения интервал необходимо будет начать заново. Таким образом, connectedCallback
здесь является лучшим выбором.
// Lit (TS)
@customElement('lit-clock')
class LitClock extends LitElement {
@state()
private date = new Date();
// initialize timerId for TS
private timerId = -1 as unknown as ReturnType<typeof setTimeout>;
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
...
}
// Lit (JS)
constructor() {
super();
// initialization
this.date = new Date();
this.timerId = -1; // initialize timerId for JS
}
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
Наконец, очистите интервал, чтобы он не выполнял галочку после отключения элемента от дерева документа.
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
Собрав все это вместе, это должно выглядеть так:
// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
@state()
private date = new Date();
private timerId = -1 as unknown as ReturnType<typeof setTimeout>;
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.date = new Date();
}
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
}
// Lit (JS)
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
static get properties() {
return {
date: {state: true}
}
}
constructor() {
super();
this.date = new Date();
}
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.date = new Date();
}
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
}
customElements.define('lit-clock', LitClock);
7. Крючки
В этом разделе вы узнаете, как перевести концепции React Hook в Lit.
Концепции хуков React
Перехваты React позволяют функциональным компонентам «подключаться» к состоянию. В этом есть несколько преимуществ.
- Они упрощают повторное использование логики с отслеживанием состояния.
- Помогите разделить компонент на более мелкие функции.
Кроме того, акцент на компонентах на основе функций позволил решить определенные проблемы с синтаксисом React на основе классов, такие как:
- Необходимость передать
props
изconstructor
вsuper
- Неаккуратная инициализация свойств в
constructor
- Это была причина, заявленная в то время командой React, но решенная ES2019.
- Проблемы, вызванные
this
больше не относятся к компоненту.
Концепции хуков React в Lit
Как упоминалось в разделе «Компоненты и реквизиты» , Lit не предлагает способа создания пользовательских элементов из функции, но LitElement решает большинство основных проблем с компонентами класса React. Например:
// React (at the time of making hooks)
import React from 'react';
import ReactDOM from 'react-dom';
class MyEl extends React.Component {
constructor(props) {
super(props); // Leaky implementation
this.state = {count: 0};
this._chart = null; // Deemed messy
}
render() {
return (
<>
<div>Num times clicked {count}</div>
<button onClick={this.clickCallback}>click me</button>
</>
);
}
clickCallback() {
// Errors because `this` no longer refers to the component
this.setState({count: this.count + 1});
}
}
// Lit (ts)
class MyEl extends LitElement {
@property({type: Number}) count = 0; // No need for constructor to set state
private _chart = null; // Public class fields introduced to JS in 2019
render() {
return html`
<div>Num times clicked ${count}</div>
<button @click=${this.clickCallback}>click me</button>`;
}
private clickCallback() {
// No error because `this` refers to component
this.count++;
}
}
Как «Лит» решает эти проблемы?
-
constructor
не принимает аргументов - Все привязки
@event
автоматически привязываются кthis
- в подавляющем большинстве случаев
this
относится к ссылке на пользовательский элемент - Свойства класса теперь могут быть созданы как члены класса. Это очищает реализации на основе конструктора.
Реактивные контроллеры
Основные концепции, лежащие в основе хуков, существуют в Lit как реактивные контроллеры . Шаблоны реактивного контроллера позволяют совместно использовать логику с сохранением состояния, разбивать компоненты на более мелкие и модульные части, а также подключаться к жизненному циклу обновления элемента.
Реактивный контроллер — это объектный интерфейс, который может подключаться к жизненному циклу обновления хоста контроллера, такого как LitElement.
Жизненный цикл ReactiveController
и reactiveControllerHost
:
interface ReactiveController {
hostConnected(): void;
hostUpdate(): void;
hostUpdated(): void;
hostDisconnected(): void;
}
interface ReactiveControllerHost {
addController(controller: ReactiveController): void;
removeController(controller: ReactiveController): void;
requestUpdate(): void;
readonly updateComplete: Promise<boolean>;
}
Создав реактивный контроллер и прикрепив его к хосту с помощью addController
, жизненный цикл контроллера будет вызываться параллельно с жизненным циклом хоста. Например, вспомните пример часов из раздела «Состояние и жизненный цикл» :
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
В приведенном выше примере есть простые часы, которые выполняют следующие действия:
- Он отображает «Hello World! Это так», а затем отображает время.
- Каждую секунду он будет обновлять часы
- При демонтаже очищает интервал вызова галочки
Создание каркаса компонентов
Сначала начните с объявления класса компонента и добавьте функцию render
.
// Lit (TS) - index.ts
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
@customElement('my-element')
class MyElement extends LitElement {
render() {
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${'time to get Lit'}.</h2>
</div>
`;
}
}
// Lit (JS) - index.js
import {LitElement, html} from 'lit';
class MyElement extends LitElement {
render() {
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${'time to get Lit'}.</h2>
</div>
`;
}
}
customElements.define('my-element', MyElement);
Создание контроллера
Теперь переключитесь на clock.ts
, создайте класс для ClockController
и настройте constructor
:
// Lit (TS) - clock.ts
import {ReactiveController, ReactiveControllerHost} from 'lit';
export class ClockController implements ReactiveController {
private readonly host: ReactiveControllerHost;
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
hostConnected() {
}
private tick() {
}
hostDisconnected() {
}
}
// Lit (JS) - clock.js
export class ClockController {
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
}
tick() {
}
hostDisconnected() {
}
}
Реактивный контроллер может быть построен любым способом, если он использует интерфейс ReactiveController
, но использование класса с constructor
, который может принимать интерфейс ReactiveControllerHost
, а также любые другие свойства, необходимые для инициализации контроллера, — это шаблон, который команда Lit предпочитает. использовать для большинства основных случаев.
Теперь вам нужно преобразовать обратные вызовы жизненного цикла React в обратные вызовы контроллера. Суммируя:
-
componentDidMount
- К
connectedCallback
LitElement - К
hostConnected
контроллераПодключено
- К
-
ComponentWillUnmount
- Для
disconnectedCallback
LitElement - К
hostDisconnected
контроллераОтключено
- Для
Дополнительную информацию о переводе жизненного цикла React в жизненный цикл Lit см. в разделе «Состояние и жизненный цикл» .
Затем реализуйте обратный вызов hostConnected
и методы tick
, а также очистите интервал в hostDisconnected
, как это сделано в примере в разделе «Состояние и жизненный цикл» .
// Lit (TS) - clock.ts
export class ClockController implements ReactiveController {
private readonly host: ReactiveControllerHost;
private interval = 0 as unknown as ReturnType<typeof setTimeout>;
date = new Date();
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
hostConnected() {
this.interval = setInterval(() => this.tick(), 1000);
}
private tick() {
this.date = new Date();
}
hostDisconnected() {
clearInterval(this.interval);
}
}
// Lit (JS) - clock.js
export class ClockController {
interval = 0;
host;
date = new Date();
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
this.interval = setInterval(() => this.tick(), 1000);
}
tick() {
this.date = new Date();
}
hostDisconnected() {
clearInterval(this.interval);
}
}
Использование контроллера
Чтобы использовать контроллер часов, импортируйте контроллер и обновите компонент в index.ts
или index.js
.
// Lit (TS) - index.ts
import {LitElement, html, ReactiveController, ReactiveControllerHost} from 'lit';
import {customElement} from 'lit/decorators.js';
import {ClockController} from './clock.js';
@customElement('my-element')
class MyElement extends LitElement {
private readonly clock = new ClockController(this); // Instantiate
render() {
// Use controller
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
</div>
`;
}
}
// Lit (JS) - index.js
import {LitElement, html} from 'lit';
import {ClockController} from './clock.js';
class MyElement extends LitElement {
clock = new ClockController(this); // Instantiate
render() {
// Use controller
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
</div>
`;
}
}
customElements.define('my-element', MyElement);
Чтобы использовать контроллер, вам необходимо создать экземпляр контроллера, передав ссылку на хост контроллера (который является компонентом <my-element>
), а затем использовать контроллер в методе render
.
Запуск повторного рендеринга в контроллере
Обратите внимание, что время отображается, но время не обновляется. Это связано с тем, что контроллер устанавливает дату каждую секунду, но хост не обновляется. Это связано с тем, что date
больше меняется в классе ClockController
, а не в компоненте. Это означает, что после того, как date
установлена на контроллере, хосту необходимо сообщить о необходимости запуска жизненного цикла обновления с помощью host.requestUpdate()
.
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
Теперь часы должны тикать!
Более подробное сравнение распространенных случаев использования перехватчиков можно найти в разделе «Продвинутые темы — перехватчики» .
8. Дети
В этом разделе вы узнаете, как использовать слоты для управления детьми в Лит.
Слоты и Дети
Слоты обеспечивают композицию, позволяя вкладывать компоненты.
В React дочерние элементы наследуются через реквизиты. Слот по умолчанию — props.children
, а функция render
определяет, где расположен слот по умолчанию. Например:
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
Имейте в виду, что props.children
— это компоненты React, а не элементы HTML.
В Lit дочерние элементы компонуются в функции рендеринга с элементами слота . Обратите внимание, что дети не наследуются так же, как React. В Lit дочерние элементы — это элементы HTML, прикрепленные к слотам. Это вложение называется Проекция .
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<slot></slot>
</article>
`;
}
}
Несколько слотов
В React добавление нескольких слотов по сути то же самое, что наследование большего количества реквизитов.
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
Аналогично, добавление дополнительных элементов <slot>
создает больше слотов в Lit. Несколько слотов определяются атрибутом name
: <slot name="slot-name">
. Это позволяет детям объявить, какой слот им будет назначен.
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<header>
<slot name="headerChildren"></slot>
</header>
<section>
<slot name="sectionChildren"></slot>
</section>
</article>
`;
}
}
Содержимое слота по умолчанию
Слоты будут отображать свое поддерево, если в этот слот не проецируются узлы. Когда узлы проецируются в слот, этот слот не отображает свое поддерево, а вместо этого отображает проецируемые узлы.
@customElement("my-element")
export class MyElement extends LitElement {
render() {
return html`
<section>
<div>
<slot name="slotWithDefault">
<p>
This message will not be rendered when children are attached to this slot!
<p>
</slot>
</div>
</section>
`;
}
}
Назначьте детей в слоты
В React дочерние элементы назначаются слотам через свойства компонента. В приведенном ниже примере элементы React передаются в реквизиты headerChildren
sectionChildren
.
const MyNewsArticle = () => {
return (
<MyArticle
headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
sectionChildren={<p>Children are props in React!</p>}
/>
);
};
В Lit дочерние элементы назначаются слотам с помощью атрибута slot
.
@customElement("my-news-article")
export class MyNewsArticle extends LitElement {
render() {
return html`
<my-article>
<h3 slot="headerChildren">
Extry, Extry! Read all about it!
</h3>
<p slot="sectionChildren">
Children are composed with slots in Lit!
</p>
</my-article>
`;
}
}
Если нет слота по умолчанию (например, <slot>
) и нет слота с атрибутом name
(например <slot name="foo">
), который соответствует атрибуту slot
дочерних элементов пользовательского элемента (например, <div slot="foo">
), то этот узел не будет проецироваться и отображаться.
9. Ссылки
Иногда разработчику может потребоваться доступ к API HTMLElement.
В этом разделе вы узнаете, как получить ссылки на элементы в Lit.
Реагировать на ссылки
Компонент React транслируется в серию вызовов функций, которые при вызове создают виртуальный DOM. Этот виртуальный DOM интерпретируется ReactDOM и отображает HTMLElements.
В React ссылки — это пространство в памяти для хранения сгенерированного HTMLElement.
const RefsExample = (props) => {
const inputRef = React.useRef(null);
const onButtonClick = React.useCallback(() => {
inputRef.current?.focus();
}, [inputRef]);
return (
<div>
<input type={"text"} ref={inputRef} />
<br />
<button onClick={onButtonClick}>
Click to focus on the input above!
</button>
</div>
);
};
В приведенном выше примере компонент React будет делать следующее:
- Отображение пустого текстового ввода и кнопки с текстом
- Фокус ввода при нажатии кнопки
После первоначального рендеринга React установит inputRef.current
в сгенерированный HTMLInputElement
через атрибут ref
.
Горит «Ссылки» с помощью @query
Lit живет рядом с браузером и создает очень тонкую абстракцию над собственными функциями браузера.
Эквивалентом refs
в Lit в React является HTMLElement, возвращаемый декораторами @query
и @queryAll
.
@customElement("my-element")
export class MyElement extends LitElement {
@query('input') // Define the query
inputEl!: HTMLInputElement; // Declare the prop
// Declare the click event listener
onButtonClick() {
// Use the query to focus
this.inputEl.focus();
}
render() {
return html`
<input type="text">
<br />
<!-- Bind the click listener -->
<button @click=${this.onButtonClick}>
Click to focus on the input above!
</button>
`;
}
}
В приведенном выше примере компонент Lit выполняет следующие действия:
- Определяет свойство
MyElement
с помощью декоратора@query
(создавая метод получения дляHTMLInputElement
). - Объявляет и присоединяет обратный вызов события клика, называемый
onButtonClick
. - Фокусирует ввод при нажатии кнопки
В JavaScript декораторы @query
и @queryAll
выполняют querySelector
и querySelectorAll
соответственно. Это JavaScript-эквивалент @query('input') inputEl!: HTMLInputElement;
get inputEl() {
return this.renderRoot.querySelector('input');
}
После того, как компонент Lit фиксирует шаблон метода render
в корне my-element
, декоратор @query
теперь позволяет inputEl
возвращать первый input
элемент, найденный в корне рендеринга. Он вернет null
, если @query
не сможет найти указанный элемент.
Если бы в корне рендеринга было несколько input
элементов, @queryAll
вернул бы список узлов.
10. Посредническое государство
В этом разделе вы узнаете, как передавать состояние между компонентами в Lit.
Многоразовые компоненты
React имитирует конвейеры функционального рендеринга с потоком данных сверху вниз. Родители обеспечивают состояние детям через реквизит. Дети общаются с родителями посредством обратных вызовов, найденных в реквизите.
const CounterButton = (props) => {
const label = props.step < 0
? `- ${-1 * props.step}`
: `+ ${props.step}`;
return (
<button
onClick={() =>
props.addToCounter(props.step)}>{label}</button>
);
};
В приведенном выше примере компонент React выполняет следующие действия:
- Создает метку на основе значения
props.step
. - Отображает кнопку с меткой +step или -step.
- Обновляет родительский компонент, вызывая
props.addToCounter
сprops.step
в качестве аргумента при щелчке.
Хотя в Lit можно передавать обратные вызовы, общепринятые шаблоны отличаются. Компонент React в приведенном выше примере можно записать как Lit Component в примере ниже:
@customElement('counter-button')
export class CounterButton extends LitElement {
@property({type: Number}) step: number = 0;
onClick() {
const event = new CustomEvent('update-counter', {
bubbles: true,
detail: {
step: this.step,
}
});
this.dispatchEvent(event);
}
render() {
const label = this.step < 0
? `- ${-1 * this.step}` // "- 1"
: `+ ${this.step}`; // "+ 1"
return html`
<button @click=${this.onClick}>${label}</button>
`;
}
}
В приведенном выше примере освещенный компонент будет делать следующее:
- Создайте
step
реактивного свойства - Отправьте пользовательское событие под названием
update-counter
, несущее значениеstep
элемента при щелчке.
События браузера переходят от дочерних элементов к родительским. События позволяют детям транслировать события взаимодействия и изменения состояния. React по сути передает состояние в противоположном направлении, поэтому редко можно увидеть, как компоненты React отправляют и прослушивают события так же, как Lit Components.
Компоненты с отслеживанием состояния
В React для управления состоянием часто используются хуки. Компонент MyCounter
можно создать путем повторного использования компонента CounterButton
. Обратите внимание, как addToCounter
передается обоим экземплярам CounterButton
.
const MyCounter = (props) => {
const [counterSum, setCounterSum] = React.useState(0);
const addToCounter = useCallback(
(step) => {
setCounterSum(counterSum + step);
},
[counterSum, setCounterSum]
);
return (
<div>
<h3>Σ: {counterSum}</h3>
<CounterButton
step={-1}
addToCounter={addToCounter} />
<CounterButton
step={1}
addToCounter={addToCounter} />
</div>
);
};
В приведенном выше примере делается следующее:
- Создает состояние
count
. - Создает обратный вызов, который добавляет число к состоянию
count
. -
CounterButton
используетaddToCounter
дляstep
обновленияcount
при каждом щелчке мыши.
Аналогичная реализация MyCounter
может быть достигнута в Lit. Обратите внимание, что addToCounter
не передается в counter-button
. Вместо этого обратный вызов привязывается как прослушиватель событий к событию @update-counter
в родительском элементе.
@customElement("my-counter")
export class MyCounter extends LitElement {
@property({type: Number}) count = 0;
addToCounter(e: CustomEvent<{step: number}>) {
// Get step from detail of event or via @query
this.count += e.detail.step;
}
render() {
return html`
<div @update-counter="${this.addToCounter}">
<h3>Σ ${this.count}</h3>
<counter-button step="-1"></counter-button>
<counter-button step="1"></counter-button>
</div>
`;
}
}
В приведенном выше примере делается следующее:
- Создает реактивное свойство под названием
count
, которое будет обновлять компонент при изменении значения. - Привязывает обратный вызов
addToCounter
к прослушивателю событий@update-counter
- Обновления
count
путем добавления значения, найденного вdetail.step
шаге событияupdate-counter
- Устанавливает значение
step
counter-button
через атрибутstep
.
В Lit более традиционно использовать реактивные свойства для передачи изменений от родителей детям. Аналогичным образом, хорошей практикой является использование системы событий браузера для отображения подробностей снизу вверх.
Этот подход соответствует передовым практикам и соответствует цели Lit по обеспечению кросс-платформенной поддержки веб-компонентов.
11. Стиль
В этом разделе вы узнаете о стилизации в Lit.
Стиль
Lit предлагает несколько способов стилизации элементов, а также встроенное решение.
Встроенные стили
Lit поддерживает встроенные стили, а также привязку к ним.
import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';
@customElement('my-element')
class MyElement extends LitElement {
render() {
return html`
<div>
<h1 style="color:orange;">This text is orange</h1>
<h1 style="color:rebeccapurple;">This text is rebeccapurple</h1>
</div>
`;
}
}
В приведенном выше примере есть два заголовка, каждый из которых имеет встроенный стиль.
Теперь импортируйте и привяжите рамку из border-color.js
к оранжевому тексту:
...
import borderColor from './border-color.js';
...
html`
...
<h1 style="color:orange;${borderColor}">This text is orange</h1>
...`
Необходимость каждый раз вычислять строку стиля может немного раздражать, поэтому Lit предлагает директиву, которая поможет в этом.
стильКарта
Директива styleMap
упрощает использование JavaScript для установки встроенных стилей. Например:
import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map.js';
@customElement('my-element')
class MyElement extends LitElement {
@property({type: String})
color = '#000'
render() {
// Define the styleMap
const headerStyle = styleMap({
'border-color': this.color,
});
return html`
<div>
<h1
style="border-style:solid;
<!-- Use the styleMap -->
border-width:2px;${headerStyle}">
This div has a border color of ${this.color}
</h1>
<input
type="color"
@input=${e => (this.color = e.target.value)}
value="#000">
</div>
`;
}
}
Приведенный выше пример делает следующее:
- Отображает
h1
с рамкой и палитрой цветов. - Изменяет
border-color
на значение из палитры цветов.
Кроме того, существует styleMap
, который используется для установки стилей h1
. styleMap
использует синтаксис, аналогичный синтаксису привязки атрибутов style
React.
CSSResult
Рекомендуемый способ стилизации компонентов — использовать литерал шаблона с тегами css
.
import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';
const ORANGE = css`orange`;
@customElement('my-element')
class MyElement extends LitElement {
static styles = [
css`
#orange {
color: ${ORANGE};
}
#purple {
color: rebeccapurple;
}
`
];
render() {
return html`
<div>
<h1 id="orange">This text is orange</h1>
<h1 id="purple">This text is rebeccapurple</h1>
</div>
`;
}
}
Приведенный выше пример делает следующее:
- Объявляет литерал шаблона с тегом CSS с привязкой
- Устанавливает цвета двух
h1
с идентификаторами
Преимущества использования тега шаблона css
:
- Анализируется один раз для каждого класса или для каждого экземпляра
- Реализовано с учетом возможности повторного использования модулей.
- Можно легко разделить стили на отдельные файлы.
- Совместимость с полифилом Custom Properties CSS.
Кроме того, обратите внимание на тег <style>
в index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Lit будет охватывать стили ваших компонентов до их корней. Это означает, что стили не будут просачиваться и исчезать. Чтобы передать стили компонентам, команда Lit рекомендует использовать пользовательские свойства CSS , поскольку они могут проникать в область действия стиля Lit.
Теги стиля
Также возможно просто встроить теги <style>
в ваши шаблоны. Браузер дедуплицирует эти теги стиля, но, поместив их в ваши шаблоны, они будут анализироваться для каждого экземпляра компонента, а не для каждого класса, как в случае с шаблоном с тегами css
. Кроме того, дедупликация CSSResult
в браузере происходит намного быстрее.
Теги ссылок
Использование <link rel="stylesheet">
в вашем шаблоне также возможно для стилей, но это также не рекомендуется, поскольку это может вызвать начальную вспышку нестилизованного контента (FOUC).
12. Расширенные темы (необязательно)
JSX и шаблоны
Освещенный и виртуальный DOM
Lit-html не включает в себя обычный виртуальный DOM, который различает каждый отдельный узел. Вместо этого он использует функции производительности, внутреннюю к литеральной спецификации, связанном с меткой ES2015. Tagged Template Literals - это шаблонные буквальные строки с подключенными к ним функциями тега.
Вот пример буквального шаблона:
const str = 'string';
console.log(`This is a template literal ${str}`);
Вот пример буквального шаблона:
const tag = (strings, ...values) => ({strings, values});
const f = (x) => tag`hello ${x} how are you`;
console.log(f('world')); // {strings: ["hello ", " how are you"], values: ["world"]}
console.log(f('world').strings === f(1 + 2).strings); // true
В приведенном выше примере тег - это функция tag
, а функция f
возвращает вызов буквального шаблона.
Многое из магии производительности в горе происходит от того факта, что строковые массивы, передаваемые в функцию тега, имеют одинаковый указатель (как показано во второй console.log
). Браузер не воссоздает новый массив strings
на каждом вызове функции тега, потому что он использует один и тот же буквальный буквальный (т.е. в одном и том же месте в AST). Таким образом, связывание, синтаксическое и шаблон LIT LIT может воспользоваться этими функциями без особых накладных расходов во время выполнения.
Это встроенное поведение браузера Tagged Template Leatrals дает горит довольно преимущество в производительности. Большинство обычных виртуальных DOMS выполняют большую часть своей работы в JavaScript. Тем не менее, Tagged Template Literals делают большую часть своего различия в C ++ браузера.
Если вы хотите начать использовать Tagged Tagged Literals HTML с React или Preact, команда Lit рекомендует библиотеку htm
.
Хотя, как и в случае с сайтом Google CodeLabs и несколькими редакторами онлайн -кода, вы заметите, что выделение литерального синтаксиса с меткой шаблона не очень распространено. Некоторые идентификаторы и текстовые редакторы поддерживают их по умолчанию, такие как atom и github's Codeblock Highlighter. Команда Lit также очень тесно сотрудничает с сообществом, чтобы поддерживать такие проекты, как lit-plugin
, который представляет собой плагин VS-кода, который добавит синтаксис-выделение, проверку типов и Intellisense к вашим Lit Projects.
Lit & JSX + React Dom
JSX не работает в браузере и вместо этого использует препроцессор для преобразования вызовов функции JSX в JAVAScript (обычно через Babel).
Например, Babel преобразует это:
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
в это:
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
React DOM затем берет результат React и переводит его на фактические свойства DOM - свойства, атрибуты, слушатели событий и все.
Lit-HTML использует Tagged Template Literals, которые могут работать в браузере без транспиляции или препроцессора. Это означает, что для того, чтобы начать работу с Lit, все, что вам нужно, это HTML -файл, скрипт модуля ES и сервер. Вот полностью управляемый браузером сценарий:
<!DOCTYPE html>
<html>
<head>
<script type="module">
import {html, render} from 'https://cdn.skypack.dev/lit';
render(
html`<div>Hello World!</div>`,
document.querySelector('.root')
)
</script>
</head>
<body>
<div class="root"></div>
</body>
</html>
Кроме того, поскольку система шаблона LIT Lit-HTML не использует обычный виртуальный DOM, а скорее использует DOM API напрямую, размер Lit 2 находится под 5 кб-мБ и GZED по сравнению с React (2,8 КБ) + React-Dom (39,4 КБ). 40 КБ заслуженно и заморожено.
События
React использует синтетическую систему событий. Это означает, что React-Dom должен определять каждое событие, которое будет использоваться на каждом компоненте, и предоставлять слушатель Camelcase Event, эквивалентный для каждого типа узла. В результате JSX не имеет метода для определения слушателя событий для пользовательского события, и разработчики должны использовать ref
, а затем применить слушателя. Это создает опыт разработчика подразделения при интеграции библиотек, которые не имеют в виду, что приводит к тому, что приводит к тому, что необходимость писать специфичную для реагирования обертку.
Lit-HTML непосредственно обращается к DOM и использует нативные события, поэтому добавление слушателей событий так же просто, как @event-name=${eventNameListener}
. Это означает, что меньше разбора времени выполнения делается для добавления слушателей событий, а также для стрельбы.
Компоненты и реквизит
React Components и пользовательские элементы
Под капотом Litelement использует пользовательские элементы для упаковки своих компонентов. Пользовательские элементы вводят некоторые компромиссы между компонентами React, когда речь идет о компонентизации (состояние и жизненный цикл обсуждаются в разделе штата и жизненного цикла ).
Некоторые преимущества пользовательские элементы имеют в качестве компонентной системы:
- Уроженец браузера и не требует никаких инструментов
- Вписать в каждый API браузера от
innerHTML
иdocument.createElement
кquerySelector
- Обычно можно использовать через рамки
- Может быть лениво зарегистрироваться в
customElements.define
и «Hydrate» dom
Некоторые недостатки пользовательских элементов сравнивают с компонентами реагирования:
- Не могу создать пользовательский элемент без определения класса (таким образом, нет JSX-подобных функциональных компонентов)
- Должен содержать закрывающий тег
- Примечание. Несмотря на то, что поставщики браузеров разработчика, как правило, сожалеют о спецификации самостоятельного тега, поэтому новые спецификации, как правило, не включают самозакрывающие теги
- Представляет дополнительный узел дерева DOM, который может вызвать проблемы с макетом
- Должен быть зарегистрирован через JavaScript
Lits пошел с пользовательскими элементами по модернизированной системе элементов, потому что пользовательские элементы встроены в браузер, и команда Lit считает, что преимущества перекрестной рамы перевешивают преимущества, предоставляемые слоем абстракции компонентов. Фактически, усилия Lit Team в пространстве Lit-SSR преодолели основные проблемы с регистрацией JavaScript. Кроме того, некоторые компании, такие как GitHub, используют пользовательскую личную регистрацию Lazy, чтобы постепенно улучшить страницы с помощью дополнительного таланта.
Передача данных в пользовательские элементы
Распространенным заблуждением с пользовательскими элементами является то, что данные могут передаваться только как строки. Это заблуждение, вероятно, исходит из того факта, что атрибуты элементов могут быть написаны только как строки. Хотя это правда, что LIT будет сдавать атрибуты строк к их определенным типам, пользовательские элементы также могут принимать сложные данные в качестве свойств.
Например, учитывая следующее определение литлета:
// data-test.ts
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('data-test')
class DataTest extends LitElement {
@property({type: Number})
num = 0;
@property({attribute: false})
data = {a: 0, b: null, c: [html`<div>hello</div>`, html`<div>world</div>`]}
render() {
return html`
<div>num + 1 = ${this.num + 1}</div>
<div>data.a = ${this.data.a}</div>
<div>data.b = ${this.data.b}</div>
<div>data.c = ${this.data.c}</div>`;
}
}
num
примитивное реактивное свойство, которое будет преобразовать строковое значение атрибута в number
, а затем введена сложная структура данных с attribute:false
, который деактивирует обработку атрибутов Lit.
Это как передавать данные в этот пользовательский элемент:
<head>
<script type="module">
import './data-test.js'; // loads element definition
import {html} from './data-test.js';
const el = document.querySelector('data-test');
el.data = {
a: 5,
b: null,
c: [html`<div>foo</div>`,html`<div>bar</div>`]
};
</script>
</head>
<body>
<data-test num="5"></data-test>
</body>
Государство и жизненный цикл
Другие обратные вызовы жизни реагируют
static getDerivedStateFromProps
Нет эквивалента, как реквизит, а состояние - это одно и то же классовое свойства
shouldComponentUpdate
- Горит эквивалент
shouldUpdate
- Призвал к первым визуализациям в отличие от React
- Аналогично по функции, чтобы реагировать
shouldComponentUpdate
getSnapshotBeforeUpdate
В Lit, getSnapshotBeforeUpdate
аналогична как update
, так и willUpdate
willUpdate
- Вызвано до
update
- В отличие от
getSnapshotBeforeUpdate
,willUpdate
вызывается передrender
- Изменения в реактивных свойствах в
willUpdate
не повторно запускают цикл обновления - Хорошее место для вычисления значений свойств, которые зависят от других свойств и используются в остальной части процесса обновления
- Этот метод вызывается на сервере в SSR, поэтому доступ к DOM здесь не рекомендуется
update
- Вызвано после
willUpdate
- В отличие от
getSnapshotBeforeUpdate
,update
вызывается передrender
- Изменения в реактивных свойствах в
update
не повторно запускайте цикл обновления, если изменяется перед вызовомsuper.update
- Хорошее место для захвата информации из DOM, окружающего компонент до того, как отображаемый вывод
- Этот метод не вызывается на сервере в SSR
Другие обратные вызовы Lit Lifecycle
Есть несколько обратных вызовов жизненного цикла, которые не были упомянуты в предыдущем разделе, потому что в React нет аналогов. Они есть:
attributeChangedCallback
Это вызывается, когда изменяется один из элементов observedAttributes
. Как observedAttributes
, так и attributeChangedCallback
являются частью спецификации пользовательских элементов и реализованы Lit под капюшоном, чтобы обеспечить API атрибута для освещенных элементов.
adoptedCallback
Призван, когда компонент перемещается в новый документ, например, из documentFragment
HTMLTemplateElement
в основной document
. Этот обратный вызов также является частью спецификации пользовательских элементов и должен использоваться только для усовершенствованных вариантов использования, когда компонент меняет документы.
Другие методы и свойства жизненного цикла
Эти методы и свойства являются членами класса, которые вы можете позвонить, переопределить или ожидать, чтобы помочь манипулировать процессом жизненного цикла.
updateComplete
Это Promise
, которое разрешается, когда элемент закончил обновление, поскольку обновление и жизненные циклы рендеринга асинхронны. Пример:
async nextButtonClicked() {
this.step++;
// Wait for the next "step" state to render
await this.updateComplete;
this.dispatchEvent(new Event('step-rendered'));
}
getUpdateComplete
Это метод, который должен быть переопределен для настройки при разрешении updateComplete
. Это распространено, когда компонент делает дочерний компонент, а их циклы рендеринга должны быть синхронизированы. например,
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
Этот метод - это то, что вызывает обратные вызовы жизненного цикла обновления. Как правило, это не должно потребоваться, за исключением редких случаев, когда обновление должно быть сделано синхронно или для пользовательского планирования.
hasUpdated
Это свойство true
, если компонент обновил хотя бы один раз.
isConnected
Часть спецификации пользовательских элементов, это свойство будет true
, если элемент в настоящее время прикреплен к основному дереву документов.
Обновление обновления визуализация жизненного цикла
В жизненном цикле обновления есть 3 части:
- Pre-update
- Обновлять
- После обновления
Pre-update
После requestUpdate
ожидается запланированное обновление.
Обновлять
После обновления
Крючки
Почему крючки
Крюки были введены в реагирование для простых вариантов использования функциональных компонентов, которые требовали состояния. Во многих простых случаях функциональные компоненты с крючками, как правило, намного проще и читаемым, чем их аналоги с компонентами класса. Хотя при введении обновлений асинхнозного состояния, а также передачи данных между крючками или эффектами, шаблон крючков имеет тенденцию недостаточно, а решение, основанное на классе, такое как реактивные контроллеры, имеют тенденцию сиять.
API -запросы крючки и контроллеры
Обычно писать крючок, который запрашивает данные из API. Например, возьмите этот компонент функции React , который выполняет следующее:
-
index.tsx
- Образует текст
- Отвечает ответу
useAPI
- Идентификатор пользователя + имя пользователя
- Сообщение об ошибке
- 404 при достижении пользователя 11 (по дизайну)
- Ошибка прерывания, если API Fetch прерван
- Загрузка сообщения
- Образует кнопку действия
- Следующий пользователь: который получает API для следующего пользователя
- Отмена: который прерывает API и отображает ошибку
-
useApi.tsx
- Определяет индивидуальный крючок
useApi
- Async извлечет пользовательский объект из API
- Излучает:
- Имя пользователя
- Загружается ли выборка
- Любые сообщения об ошибках
- Обратный вызов, чтобы прервать выборку
- Прерывает избрать в процессе
- Определяет индивидуальный крючок
Вот реализация реагирующего контроллера Lit + .
Вынос:
- Реактивные контроллеры больше всего похожи на индивидуальные крючки
- Передача неразличиваемых данных между обратными вызовами и эффектами
- React использует
useRef
для передачи данных междуuseEffect
иuseCallback
- LIT использует собственность частного класса
- Реакция по существу имитирует поведение собственности частного класса
- React использует
Кроме того, если вам действительно нравится синтаксис функциональных компонентов React с крючками, но та же самая среда Lit, Lit, команда LIT настоятельно рекомендует библиотеку с привидениями .
Дети
Слот по умолчанию
Когда HTML -элементы не получают атрибут slot
, они присваиваются неназванному слоту по умолчанию. В приведенном ниже примере MyApp
вставит один абзац в именованный слот. Другой абзац по умолчанию в неназванный слот ".
@customElement("my-element")
export class MyElement extends LitElement {
render() {
return html`
<section>
<div>
<slot></slot>
</div>
<div>
<slot name="custom-slot"></slot>
</div>
</section>
`;
}
}
@customElement("my-app")
export class MyApp extends LitElement {
render() {
return html`
<my-element>
<p slot="custom-slot">
This paragraph will be placed in the custom-slot!
</p>
<p>
This paragraph will be placed in the unnamed default slot!
</p>
</my-element>
`;
}
}
Обновления слотов
Когда структура потомков слота меняется, запускается событие slotchange
. Горит компонент может привязаться к выставлению событий с событием slotchange
. В приведенном ниже примере первый слот, найденной в shadowRoot
будет назначать их, зарегистрированные на консоли на slotchange
.
@customElement("my-element")
export class MyElement extends LitElement {
onSlotChange(e: Event) {
const slot = this.shadowRoot.querySelector('slot');
console.log(slot.assignedNodes({flatten: true}));
}
render() {
return html`
<section>
<div>
<slot @slotchange="{this.onSlotChange}"></slot>
</div>
</section>
`;
}
}
Ссылки
Справочный поколение
Горит и реагировать оба выставьте ссылку на HTMlelement после того, как их функции render
были вызваны. Но стоит просмотреть, как React and Lit составьте DOM, который позже возвращается через маленький декоратор @query
или справочник React.
React - это функциональный трубопровод, который создает компоненты React, а не HTMlelements. Поскольку рефери объявляется до того, как будет отображаться HTMlelement, распределяется пространство в памяти. Вот почему вы видите null
как начальное значение рефери, потому что фактический элемент DOM еще не был создан (или отображается), т. Е. useRef(null)
.
После того, как Reactdom преобразует реагированный компонент в HTMlelement, он ищет атрибут, называемый ref
в ReactComponent. Если доступно, Reactdom помещает ссылку HTMleLement на ref.current
.
LiteLement использует функцию метки шаблона html
от Lit-HTML для составления элемента шаблона под капотом. Litelement отмечает содержимое шаблона в теневую DOM пользовательского элемента после рендеринга. The Shadow Dom - это дерево Dom, инкапсулированное корнем тени. Затем декоратор @query
создает Getter для собственности, которая по сути выполняет this.shadowRoot.querySelector
на корне смельку.
Запросить несколько элементов
В приведенном ниже примере декоратор @queryAll
вернет два абзаца в корне Shadow в качестве NodeList
.
@customElement("my-element")
export class MyElement extends LitElement {
@queryAll('p')
paragraphs!: NodeList;
render() {
return html`
<p>Hello, world!</p>
<p>How are you?</p>
`;
}
}
По сути, @queryAll
создает Getter для paragraphs
, которые возвращают результаты this.shadowRoot.querySelectorAll()
. В JavaScript можно объявить, что выполнять ту же цель:
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
Элементы изменения запроса
Декоратор @queryAsync
лучше подходит для обработки узла, который может измениться на основе состояния другого свойства элемента.
В приведенном ниже примере @queryAsync
найдет первый элемент абзаца. Тем не менее, элемент абзаца будет отображаться только тогда, когда renderParagraph
случайным образом генерирует нечетное число. Директива @queryAsync
вернет обещание, которое будет решено, когда будет доступен первый абзац.
@customElement("my-dissappearing-paragraph")
export class MyDisapppearingParagraph extends LitElement {
@queryAsync('p')
paragraph!: Promise<HTMLElement>;
renderParagraph() {
const randomNumber = Math.floor(Math.random() * 10)
if (randomNumber % 2 === 0) {
return "";
}
return html`<p>This checkbox is checked!`
}
render() {
return html`
${this.renderParagraph()}
`;
}
}
Посреднивание состояния
В React соглашение состоит в том, чтобы использовать обратные вызовы, потому что состояние опосредовано самим React. React делает это лучше, чтобы не полагаться на состояние, предоставленное элементами. Дом является просто эффектом процесса рендеринга.
Внешнее состояние
Можно использовать Redux, Mobx или любую другую библиотеку управления государством, вместе с Lit.
Lit Components создаются в сфере браузера. Таким образом, любая библиотека, которая также существует в сфере охвата браузера, доступна для Lit. Многие удивительные библиотеки были построены для использования существующих систем управления государством в лит.
Вот серия Ваадина, объясняющая, как использовать Redux в освещенном компоненте.
Взгляните на Lit-Mobx от Adobe, чтобы увидеть, как крупномасштабный сайт может использовать MOBX в голени.
Кроме того, проверьте элементы Apollo , чтобы увидеть, как разработчики включают GraphQL в свои веб -компоненты.
Lit Works с собственными функциями браузера и большинством решений по управлению состоянием в сфере охвата браузера можно использовать в освещенном компоненте.
Стиль
Тень ДОМ
Чтобы изначально инкапсулировать стили и DOM в пользовательском элементе, LIT использует Shadow Dom . Корни тени генерируют теневое дерево отдельно от основного дерева документов. Это означает, что большинство стилей подключены к этому документу. Некоторые стили протекают, такие как цвет и другие стили, связанные с шрифтом.
Shadow Dom также вводит новые концепции и селекторы для спецификации CSS:
:host, :host(:hover), :host([hover]) {
/* Styles the element in which the shadow root is attached to */
}
slot[name="title"]::slotted(*), slot::slotted(:hover), slot::slotted([hover]) {
/*
* Styles the elements projected into a slot element. NOTE: the spec only allows
* styling the direcly slotted elements. Children of those elements are not stylable.
*/
}
Обмен стилями
LIT позволяет легко делиться стилями между компонентами в виде CSSTemplateResults
через теги шаблонов css
. Например:
// typography.ts
export const body1 = css`
.body1 {
...
}
`;
// my-el.ts
import {body1} from './typography.ts';
@customElement('my-el')
class MyEl Extends {
static get styles = [
body1,
css`/* local styles come after so they will override bod1 */`
]
render() {
return html`<div class="body1">...</div>`
}
}
Тематическое
Корни тени представляют собой небольшую проблему для обычных тематических тем, которые обычно представляют собой подходы тега нисходящего стиля. Обычный способ справиться с темами с помощью веб -компонентов, которые используют Shadow DOM, - это выявить стиль API с помощью пользовательских свойств CSS . Например, это шаблон, который использует дизайн материала:
.mdc-textfield-outline {
border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
caret-color: var(--mdc-theme-primary, #...);
}
Пользователь затем изменит тему сайта, применяя пользовательские значения свойств:
html {
--mdc-theme-primary: #F00;
}
html[dark] {
--mdc-theme-primary: #F88;
}
Если тема сверху вниз является обязательной, и вы не можете выставить стили, всегда можно отключить DOM, переопределив createRenderRoot
, чтобы вернуть this
, который затем отобразит шаблон ваших компонентов в сам элемент, а не в прикрепленный теневой корень, прикрепленный к пользовательскому элементу. С этим вы потеряете: инкапсуляция стиля, инкапсуляция DOM и слоты.
Производство
Т.е. 11
Если вам нужно поддерживать более старые браузеры, такие как IE 11, вам придется загрузить несколько полифиллов, которые выходят примерно на 33 КБ. Больше информации можно найти здесь .
Условные пучки
Горит команда рекомендует подавать две разные пачки, одну для IE 11 и одну для современных браузеров. Есть несколько преимуществ этого:
- Обслуживание ES 6 быстрее и будет обслуживать большинство ваших клиентов
- Транспилированный ES 5 значительно увеличивает размер пакета
- Условные пучки дают вам лучшее из обоих миров
- Т.е. 11 поддержка
- Нет замедления современных браузеров
Более подробную информацию о том, как построить кондиционерный пакет, можно найти на нашем сайте документации здесь .