Контекст¶
Контекст — это способ сделать данные доступными для целых поддеревьев компонентов без необходимости вручную привязывать свойства к каждому компоненту. Данные доступны "контекстно", так что элементы-предки, находящиеся между поставщиком и потребителем данных, даже не знают о них.
Реализация контекста Lit доступна в пакете @lit/context
:
1 |
|
Context полезен для данных, которые должны потребляться большим количеством компонентов — например, хранилищем данных приложения, текущим пользователем, темой пользовательского интерфейса — или когда привязка данных невозможна, например, когда элемент должен предоставлять данные своим легким дочерним элементам DOM.
Context очень похож на React's Context или на системы инъекции зависимостей, такие как Angular's, с некоторыми важными отличиями, которые позволяют Context работать с динамической природой DOM и обеспечивают совместимость между различными библиотеками веб-компонентов, фреймворками и обычным JavaScript.
Пример¶
Использование контекста подразумевает наличие контекстного объекта (иногда называемого ключом), провайдера и потребителя, которые взаимодействуют с помощью контекстного объекта.
Определение контекста (logger-context.ts
):
1 2 3 4 5 |
|
Провайдер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Потребитель:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Ключевые понятия¶
Протокол контекста¶
Контекст Lit основан на протоколе Context Community Protocol, разработанном группой W3C Web Components Community Group.
Этот протокол обеспечивает взаимодействие между элементами (или даже неэлементным кодом) независимо от того, как они были созданы. С помощью контекстного протокола элемент, основанный на Lit, может предоставлять данные потребителю, не построенному на Lit, или наоборот.
Контекстный протокол основан на событиях DOM. Потребитель запускает событие context-request
, которое несет в себе нужный ему контекстный ключ, а любой элемент над ним может прослушать событие context-request
и предоставить данные для этого контекстного ключа.
@lit/context
реализует этот основанный на событиях протокол и делает его доступным с помощью нескольких реактивных контроллеров и декораторов.
Объекты контекста¶
Контексты идентифицируются контекстными объектами или контекстными ключами. Это объекты, которые представляют некоторые потенциальные данные, подлежащие совместному использованию идентичностью контекстного объекта. Вы можете думать о них как о ключах Map.
Провайдеры¶
Провайдеры обычно являются элементами (но могут быть и любыми обработчиками событий), которые предоставляют данные для определенных ключей контекста.
Потребители¶
Потребители запрашивают данные для определенных ключей контекста.
Подписки¶
Когда потребитель запрашивает данные для контекста, он может сообщить провайдеру, что хочет подписаться на изменения в контексте. Если у провайдера появятся новые данные, потребитель получит уведомление и сможет автоматически их обновить.
Использование¶
Определение контекста¶
Каждое использование контекста должно иметь объект контекста для координации запроса данных. Этот объект контекста представляет личность и тип предоставляемых данных.
Объекты контекста создаются с помощью функции createContext()
:
1 2 3 |
|
Рекомендуется помещать объекты контекста в отдельный модуль, чтобы их можно было импортировать независимо от конкретных поставщиков и потребителей.
Проверка типа контекста¶
Функция createContext()
принимает любое значение и возвращает его напрямую. В TypeScript значение приводится к типизированному объекту Context
, который несет в себе тип контекста значение.
В случае ошибки, подобной этой:
1 2 3 4 5 6 |
|
TypeScript предупредит, что тип string
не может быть присвоен типу Logger
. Обратите внимание, что в настоящее время эта проверка выполняется только для публичных полей.
Равенство контекста¶
Объекты контекста используются провайдерами для сопоставления события контекстного запроса со значением. Контексты сравниваются на основе строгого равенства (===
), поэтому провайдер будет обрабатывать контекстный запрос только в том случае, если его контекстный ключ равен контекстному ключу запроса.
Это означает, что существует два основных способа создания объекта контекста:
- С помощью глобально уникального значения, например, объекта (
{}
) или символа (Symbol()
) - Со значением, которое не является глобально уникальным, так что оно может быть равным при строгом равенстве, как строка (
'logger'
) или глобальный символ (Symbol.for('logger')
).
Если вы хотите, чтобы два раздельных вызова createContext()
ссылались на один и тот же контекст, то используйте ключ, который будет равен при строгом равенстве, например строку:
1 2 |
|
Однако помните, что два модуля в вашем приложении могут использовать один и тот же контекстный ключ для ссылки на разные объекты. Чтобы избежать непреднамеренных коллизий, вы можете использовать относительно уникальную строку, например, 'console-logger'
вместо 'logger'
.
Обычно лучше всего использовать глобально уникальный контекстный объект. Символы — один из самых простых способов сделать это.
Предоставление контекста¶
В @lit/context
есть два способа предоставить значение контекста: контроллер ContextProvider
и декоратор @provide()
.
@provide()
¶
Декоратор @provide()
— это самый простой способ предоставить значение, если вы используете декораторы. Он создает для вас контроллер ContextProvider.
Украсьте свойство с помощью @provide()
и передайте ему ключ контекста:
1 2 3 4 5 6 7 8 9 |
|
Вы можете сделать свойство также реактивным с помощью @property()
или @state()
, чтобы его установка обновляла элемент провайдера, а также потребителей контекста.
1 2 3 |
|
Свойства контекста часто должны быть приватными. Вы можете сделать приватные свойства реактивными с помощью @state()
:
1 2 3 |
|
Если сделать свойство контекста публичным, элемент может предоставить публичное поле своему дочернему дереву:
1 2 3 |
|
ContextProvider¶
ContextProvider
— это реактивный контроллер, который управляет обработчиками событий context-request
за вас.
1 2 3 4 5 6 7 8 9 |
|
ContextProvider
может принимать начальное значение в качестве опции в конструкторе:
1 |
|
Или вы можете вызвать setValue()
:
1 |
|
Потребление контекста¶
Декоратор @consume()
¶
Декоратор @consume()
— это самый простой способ потребления значения, если вы используете декораторы. Он создает для вас контроллер ContextConsumer.
Украсьте свойство с помощью @consume()
и передайте ему ключ контекста:
1 2 3 4 5 6 7 8 |
|
Когда этот элемент подключается к документу, он автоматически запускает событие context-request
, получает предоставленное значение, присваивает его свойству и запускает обновление элемента.
ContextConsumer¶
ContextConsumer — это реактивный контроллер, который управляет диспетчеризацией события context-request
за вас. Контроллер будет заставлять элемент-хост обновляться при получении новых значений. Предоставленное значение будет доступно в свойстве .value
контроллера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Подписка на контексты¶
Потребители могут подписываться на значения контекста, так что если у провайдера появляется новое значение, он может передать его всем подписанным потребителям, заставив их обновиться.
Подписаться можно с помощью декоратора @consume()
:
1 2 |
|
и контроллера ContextConsumer:
1 2 3 4 5 6 |
|
Примеры использования¶
Текущий пользователь, локаль и т. д.¶
Наиболее распространенные случаи использования контекста связаны с данными, которые являются глобальными для страницы и, возможно, нужны лишь немногим компонентам на странице. Без контекста возможно, что большинство или все компоненты должны принимать и распространять реактивные свойства для данных.
Сервисы¶
Глобальные сервисы приложения, такие как логгеры, аналитика, хранилища данных, могут быть предоставлены контекстом. Преимущество контекста перед импортом из общего модуля — в позднем связывании и древовидном копировании, которые обеспечивает контекст. Тесты могут легко предоставлять имитируемые сервисы, или разные части страницы могут быть предоставлены различным экземплярам сервисов.
Темы¶
Темы — это наборы стилей, которые применяются ко всей странице или целым поддеревьям внутри страницы — именно такой охват данных, который обеспечивает контекст.
Одним из способов построения системы тем было бы определение типа Theme
, который могут предоставлять контейнеры и который содержит именованные стили. Элементы, которые хотят применить тему, могут использовать объект темы и искать стили по имени. Пользовательские реактивные контроллеры тем могут обернуть ContextProvider и ContextConsumer, чтобы уменьшить количество шаблонов.
Плагины на основе HTML¶
Контекст можно использовать для передачи данных от родителя к его легким DOM-детям. Поскольку родитель обычно не создает легкие DOM-дочерние элементы, он не может использовать привязку данных на основе шаблона для передачи им данных, но он может слушать и отвечать на события context-request
.
Например, рассмотрим элемент редактора кода с плагинами для разных языковых режимов. Вы можете сделать простую HTML-систему для добавления функций с помощью контекста:
1 2 3 4 |
|
В этом случае <code-editor>
будет предоставлять API для добавления языковых режимов через контекст, а подключаемые элементы будут использовать этот API и добавлять себя в редактор.
Форматировщики данных, генераторы ссылок и т. д.¶
Иногда многократно используемые компоненты должны форматировать данные или URL-адреса специфическим для приложения способом. Например, просмотрщик документации, который отображает ссылку на другой элемент. Компонент не будет знать URL-пространство приложения.
В таких случаях компонент может зависеть от функции, предоставляемой контекстом, которая применит специфическое для приложения форматирование к данным или ссылке.
API¶
Эта документация по API является кратким обзором до тех пор, пока не появятся готовые документы по API
createContext()
¶
Создает типизированный объект Context
Импорт:
1 |
|
Синтаксис:
1 2 3 |
|
Контексты сравниваются с помощью строгого равенства.
Если вы хотите, чтобы два отдельных вызова createContext()
ссылались на один и тот же контекст, то используйте ключ, который будет равен при строгом равенстве, как строка для Symbol.for()
:
1 2 3 4 5 |
|
Если вы хотите, чтобы контекст был уникальным и гарантированно не сталкивался с другими контекстами, используйте ключ, который уникален при строгом равенстве, например Symbol()
или object
:
1 2 3 4 5 |
|
Параметр типа ValueType
— это тип значения, которое может быть предоставлено данным контекстом. Он используется для предоставления точных типов в других API контекста.
@provide()
¶
Декоратор свойств, который добавляет контроллер ContextProvider в компонент, заставляя его реагировать на любые события context-request
от дочерних потребителей.
Импорт:
1 |
|
Синтаксис:
1 |
|
@consume()
¶
Декоратор свойств, добавляющий в компонент контроллер ContextConsumer
, который будет получать значение свойства по протоколу Context
.
Импорт:
1 |
|
Синтаксис:
1 |
|
По умолчанию значение subscribe
равно false
. Установите значение true
, чтобы подписаться на обновления значения, предоставленного контекстом.
ContextProvider
¶
ReactiveController
, который добавляет поведение поставщика контекста в пользовательский элемент, слушая события context-request
.
Импорт:
1 |
|
Конструктор:
1 2 3 4 5 6 7 |
|
Члены:
-
setValue(v: T, force = false): void
Устанавливает указанное значение и уведомляет всех подписанных потребителей о новом значении, если оно изменилось.
force
вызывает уведомление, даже если значение не изменилось, что может быть полезно при глубоком изменении свойств объекта.
ContextConsumer
¶
Реактивный контроллер, который добавляет поведение потребления контекста к пользовательскому элементу, отправляя события context-request
.
Импорт:
1 |
|
Конструктор:
1 2 3 4 5 6 7 8 |
|
Члены:
-
value: ContextType<C>
Текущее значение контекста.
Когда хост-элемент подключается к документу, он испускает событие context-request
с ключом контекста. Когда запрос контекста будет удовлетворен, контроллер вызовет обратный вызов, если он присутствует, и запустит обновление хоста, чтобы он мог отреагировать на новое значение.
Он также вызовет метод dispose, предоставленный провайдером, когда элемент хоста будет отключен.
ContextRoot
¶
ContextRoot может использоваться для сбора неудовлетворенных контекстных запросов и их повторной отправки при появлении новых провайдеров, удовлетворяющих соответствующим контекстным ключам. Это позволяет добавлять провайдеров в дерево DOM или обновлять его вслед за потребителями.
Импорт:
1 |
|
Конструктор:
1 |
|
Члены:
-
attach(element: HTMLElement): void
Прикрепляет
ContextRoot
к этому элементу и начинает прослушивать событияcontext-request
. -
detach(element: HTMLElement): void
Отсоединяет корень
ContextRoot
от этого элемента, перестает слушать событияcontext-request
.
ContextRequestEvent
¶
Событие, запускаемое потребителями для запроса значения контекста. API и поведение этого события определены Context Protocol.
Импорт:
1 |
|
context-request
всплывает и композируется.
Члены:
-
readonly context: C
Объект контекста, для которого это событие запрашивает значение
-
readonly callback: ContextCallback<ContextType<C>>
Функция, которую нужно вызвать для получения значения контекста
-
readonly subscribe?: boolean
Желает ли потребитель подписаться на новые значения контекста.
ContextCallback
¶
Обратный вызов, который предоставляется запросчиком контекста и вызывается со значением, удовлетворяющим запросу.
Этот обратный вызов может быть вызван несколько раз поставщиками контекста по мере изменения запрашиваемого значения.
Импорт:
1 |
|
Синтаксис:
1 2 3 4 |
|