內容
內容是一種讓資料可供整個元件子樹使用的方法,而無需手動將屬性繫結到每個元件。 資料是「上下文」可用的,因此,資料提供者和資料消費者之間的祖先元素甚至都不知道它的存在。
Lit 的內容實作可在 @lit/context
套件中取得
npm i @lit/context
內容適用於需要由各種且大量元件取用的資料,例如應用程式的資料儲存、目前使用者、UI 主題,或當資料繫結不是選項時,例如當元素需要將資料提供給其 light DOM 子系時。
內容與 React 的 Context 或 Angular 的依賴注入系統非常相似,但有一些重要的差異,使內容適用於 DOM 的動態本質,並實現跨不同 Web 元件函式庫、框架和純 JavaScript 的互通性。
使用內容涉及 *內容物件*(有時稱為鍵)、*提供者* 和 *消費者*,它們使用內容物件進行通訊。
內容定義 (logger-context.ts
)
import {createContext} from '@lit/context';
import type {Logger} from 'my-logging-library';
export type {Logger} from 'my-logging-library';
export const loggerContext = createContext<Logger>('logger');
提供者
import {LitElement, property, html} from 'lit';
import {provide} from '@lit/context';
import {Logger} from 'my-logging-library';
import {loggerContext} from './logger-context.js';
@customElement('my-app')
class MyApp extends LitElement {
@provide({context: loggerContext})
logger = new Logger();
render() {
return html`...`;
}
}
消費者
import {LitElement, property} from 'lit';
import {consume} from '@lit/context';
import {type Logger, loggerContext} from './logger-context.js';
export class MyElement extends LitElement {
@consume({context: loggerContext})
@property({attribute: false})
public logger?: Logger;
private doThing() {
this.logger?.log('A thing was done');
}
}
關鍵概念
連結到「關鍵概念」的永久連結內容協定
連結到「內容協定」的永久連結Lit 的內容基於 W3C 的 內容社群協定,由 Web 元件社群小組制定。
此協定可讓元素(甚至非元素程式碼)之間實現互通性,而與它們的建構方式無關。 透過內容協定,基於 Lit 的元素可以將資料提供給未使用 Lit 建構的消費者,反之亦然。
內容協定基於 DOM 事件。 消費者會觸發一個攜帶其所需內容鍵的 context-request
事件,且其上方的任何元素都可以監聽 context-request
事件並為該內容鍵提供資料。
@lit/context
實作了此基於事件的協定,並透過一些響應式控制器和裝飾器使其可用。
內容物件
連結到「內容物件」的永久連結內容由內容物件或內容鍵識別。 它們是物件,代表要由內容物件識別共用的一些潛在資料。 您可以將它們視為類似於 Map 鍵。
提供者通常是元素(但可以是任何事件處理程式碼),它們為特定的內容鍵提供資料。
消費者會請求特定內容鍵的資料。
當消費者請求內容的資料時,它可以告知提供者它想要訂閱內容的變更。 如果提供者有新資料,則會通知消費者,且可以自動更新。
使用方式
連結到「使用方式」的永久連結定義內容
連結到「定義內容」的永久連結每次使用內容都必須有一個內容物件來協調資料請求。 此內容物件代表所提供資料的識別和類型。
內容物件是使用 createContext()
函式建立的
export const myContext = createContext(Symbol('my-context'));
建議將內容物件放在它們自己的模組中,以便它們可以獨立於特定的提供者和消費者匯入。
內容類型檢查
連結到「內容類型檢查」的永久連結createContext()
接受任何值並直接傳回。 在 TypeScript 中,該值會轉換為類型化的 Context
物件,該物件會隨之攜帶內容值的類型。
如果發生像這樣的錯誤
const myContext = createContext<Logger>(Symbol('logger'));
class MyElement extends LitElement {
@provide({context: myContext})
name: string
}
TypeScript 會警告類型 string
無法指派給類型 Logger
。 請注意,此檢查目前僅適用於公開欄位。
內容相等性
連結到「內容相等性」的永久連結提供者會使用內容物件來將內容請求事件與值進行比對。 內容會以嚴格相等 (===
) 進行比較,因此,只有在其內容鍵等於請求的內容鍵時,提供者才會處理內容請求。
這表示有兩種主要方法可以建立內容物件
- 使用全域唯一的值,例如物件 (
{}
) 或符號 (Symbol()
) - 使用非全域唯一的值,以便在嚴格相等下可以相等,例如字串 (
'logger'
) 或全域符號 (Symbol.for('logger')
)。
如果您希望兩個個別的 createContext()
呼叫參照相同的內容,則請使用在嚴格相等下會相等的鍵,例如字串
// true
createContext('my-context') === createContext('my-context')
但請注意,您應用程式中的兩個模組可以使用相同的內容鍵來參照不同的物件。 若要避免意外衝突,您可能會想要使用相對唯一的字串,例如 'console-logger'
而不是 'logger'
。
通常最好使用全域唯一的內容物件。 符號是執行此操作的最簡單方法之一。
提供內容
連結到「提供內容」的永久連結在 @lit/context
中,有兩種方法可以提供內容值:ContextProvider 控制器和 @provide()
裝飾器。
@provide()
連結到「@provide()」的永久連結 如果您正在使用裝飾器,則 @provide()
裝飾器是提供值最簡單的方式。 它會為您建立 ContextProvider 控制器。
使用 @provide()
裝飾屬性並給予它內容鍵
import {LitElement, html} from 'lit';
import {property} from 'lit/decorators.js';
import {provide} from '@lit/context';
import {myContext, MyData} from './my-context.js';
class MyApp extends LitElement {
@provide({context: myContext})
myData: MyData;
}
您也可以使用 @property()
或 @state()
使屬性也成為響應式屬性,以便設定它也會更新提供者元素以及內容消費者。
@provide({context: myContext})
@property({attribute: false})
myData: MyData;
內容屬性通常旨在設為私有。 您可以使用 @state()
使私有屬性成為響應式屬性
@provide({context: myContext})
@state()
private _myData: MyData;
將內容屬性設為公開,可讓元素將公開欄位提供給其子樹
html`<my-provider-element .myData=${someData}>`
ContextProvider
連結到「ContextProvider」的永久連結ContextProvider
是一個響應式控制器,可為您管理 context-request
事件處理常式。
import {LitElement, html} from 'lit';
import {ContextProvider} from '@lit/context';
import {myContext} from './my-context.js';
export class MyApp extends LitElement {
private _provider = new ContextProvider(this, {context: myContext});
}
ContextProvider 可以在建構函式中採用初始值作為選項
private _provider = new ContextProvider(this, {context: myContext, initialValue: myData});
或者您可以呼叫 setValue()
this._provider.setValue(myData);
取用內容
連結到「取用內容」的永久連結@consume()
裝飾器
連結到「@consume() 裝飾器」的永久連結 如果您正在使用裝飾器,則 @consume()
裝飾器是取用值最簡單的方式。 它會為您建立 ContextConsumer 控制器。
使用 @consume()
裝飾屬性並給予它內容鍵
import {LitElement, html} from 'lit';
import {consume} from '@lit/context';
import {myContext, MyData} from './my-context.js';
class MyElement extends LitElement {
@consume({context: myContext})
myData: MyData;
}
當此元素連接到文件時,它會自動觸發 context-request
事件、取得提供的值、將其指派給屬性,並觸發元素的更新。
ContextConsumer
連結到「ContextConsumer」的永久連結ContextConsumer 是一個響應式控制器,可為您管理 context-request
事件的發送。 當提供新值時,控制器會導致主機元素更新。 然後,可以在控制器的 .value
屬性中找到提供的值。
import {LitElement, property} from 'lit';
import {ContextConsumer} from '@lit/context';
import {myContext} from './my-context.js';
export class MyElement extends LitElement {
private _myData = new ContextConsumer(this, {context: myContext});
render() {
const myData = this._myData.value;
return html`...`;
}
}
訂閱內容
連結到「訂閱內容」的永久連結消費者可以訂閱內容值,以便如果提供者有新值,它可以將其提供給所有已訂閱的消費者,從而導致它們更新。
您可以使用 @consume()
裝飾器進行訂閱
@consume({context: myContext, subscribe: true})
myData: MyData;
以及 ContextConsumer 控制器
private _myData = new ContextConsumer(this,
{
context: myContext,
subscribe: true,
}
);
範例使用案例
連結到「範例使用案例」的永久連結目前使用者、地區設定等
連結到「目前使用者、地區設定等」的永久連結最常見的內容使用案例涉及全域頁面的資料,並且可能僅在整個頁面的元件中零星需要。 如果沒有內容,則大多數或所有元件可能都需要接受和傳播資料的響應式屬性。
應用程式全域服務(例如記錄器、分析、資料儲存)可以由內容提供。 相較於從通用模組匯入,內容的優點是內容提供的後期耦合和樹狀範圍。 測試可以輕鬆提供模擬服務,或者可以為頁面的不同部分提供不同的服務執行個體。
主題是一組樣式,適用於整個頁面或頁面內的整個子樹 - 正好是 context 提供的那種資料範圍。
建立主題系統的一種方法是定義一個 Theme
類型,讓容器可以提供該類型,其中包含已命名的樣式。想要套用主題的元素可以取用主題物件,並透過名稱查詢樣式。自訂主題反應式控制器可以包裝 ContextProvider 和 ContextConsumer,以減少重複程式碼。
基於 HTML 的外掛程式
連結到「基於 HTML 的外掛程式」Context 可以用來將資料從父元素傳遞到其輕量 DOM 子元素。由於父元素通常不會建立輕量 DOM 子元素,因此它無法利用基於樣板的資料繫結將資料傳遞給它們,但它可以監聽並回應 context-request
事件。
例如,考慮一個具有不同語言模式外掛程式的程式碼編輯器元素。您可以使用 context 建立一個純 HTML 系統來新增功能
<code-editor>
<code-editor-javascript-mode></code-editor-javascript-mode>
<code-editor-python-mode></code-editor-python-mode>
</code-editor>
在這種情況下,<code-editor>
將提供一個 API,透過 context 新增語言模式,而外掛程式元素將取用該 API 並將自身新增到編輯器中。
資料格式器、連結產生器等
連結到「資料格式器、連結產生器等」有時,可重複使用的元件會需要以應用程式特定的方式格式化資料或 URL。例如,一個文件檢視器會呈現另一個項目的連結。該元件不會知道應用程式的 URL 空間。
在這些情況下,該元件可以依賴 context 提供的函式,該函式會將應用程式特定的格式套用至資料或連結。
API
連結到「API」這些 API 文件在產生 API 文件之前是摘要。
createContext()
連結到「createContext()」 建立一個具型別的 Context 物件。
匯入:
import {createContext} from '@lit/context';
簽章:
function createContext<ValueType, K = unknown>(key: K): Context<K, ValueType>;
Context 是以嚴格相等的方式進行比較。
如果您希望兩個獨立的 createContext()
呼叫參照相同的 context,則請使用在嚴格相等條件下會相等的鍵,例如 Symbol.for()
的字串。
// true
createContext('my-context') === createContext('my-context')
// true
createContext(Symbol.for('my-context')) === createContext(Symbol.for('my-context'))
如果您希望 context 是唯一的,以確保它不會與其他 context 衝突,請使用在嚴格相等條件下唯一的鍵,例如 Symbol()
或物件。
// false
createContext(Symbol('my-context')) === createContext(Symbol('my-context'))
// false
createContext({}) === createContext({})
ValueType
類型參數是此 context 可以提供的值的類型。它用於在其他 context API 中提供準確的類型。
@provide()
連結到「@provide()」的永久連結 一個屬性裝飾器,將 ContextProvider 控制器新增到元件,使其回應來自其子消費者端的任何 context-request
事件。
匯入:
import {provide} from '@lit/context';
簽章:
@provide({context: Context})
@consume()
連結到「@consume()」 一個屬性裝飾器,將 ContextConsumer 控制器新增到元件,它將透過 Context 協定取得屬性的值。
匯入:
import {consume} from '@lit/context';
簽章:
@consume({context: Context, subscribe?: boolean})
預設情況下,subscribe
為 false
。將其設定為 true
以訂閱對 context 提供的值的更新。
ContextProvider
連結到「ContextProvider」的永久連結 一個 ReactiveController,透過監聽 context-request
事件,將 context 提供者行為新增到自訂元素。
匯入:
import {ContextProvider} from '@lit/context';
建構函式:
ContextProvider(
host: ReactiveElement,
options: {
context: T,
initialValue?: ContextType<T>
}
)
成員
setValue(v: T, force = false): void
設定提供的值,並在值變更時通知任何訂閱的消費者新值。即使值沒有變更,
force
也會導致通知,如果物件有深層屬性變更,這可能會很有用。
ContextConsumer
連結到「ContextConsumer」的永久連結 一個 ReactiveController,透過分派 context-request
事件,將 context 消費行為新增到自訂元素。
匯入:
import {ContextConsumer} from '@lit/context';
建構函式:
ContextConsumer(
host: HostElement,
options: {
context: C,
callback?: (value: ContextType<C>, dispose?: () => void) => void,
subscribe?: boolean = false
}
)
成員
value: ContextType<C>
context 的目前值。
當主機元素連接到文件時,它會發出一個 context-request
事件,其中包含其 context 鍵。當 context 請求獲得滿足時,控制器會叫用回呼(如果存在),並觸發主機更新,以便它可以回應新值。
當主機元素斷開連接時,它也會呼叫提供者提供的 dispose 方法。
ContextRoot
連結到「ContextRoot」 ContextRoot 可用於收集未滿足的 context 請求,並在新的提供者提供符合的 context 鍵時重新分派它們。這允許在消費者之後將提供者新增到 DOM 樹狀結構,或進行升級。
匯入:
import {ContextRoot} from '@lit/context';
建構函式:
ContextRoot()
成員
attach(element: HTMLElement): void
將 ContextRoot 連接到此元素,並開始監聽
context-request
事件。detach(element: HTMLElement): void
從此元素分離 ContextRoot,並停止監聽
context-request
事件。
ContextRequestEvent
連結到「ContextRequestEvent」 消費者發出的事件,用於請求 context 值。此事件的 API 和行為由Context 協定指定。
匯入:
import {ContextRequestEvent} from '@lit/context';
context-request
會冒泡且是複合的。
成員
readonly context: C
此事件正在請求值的 context 物件
readonly callback: ContextCallback<ContextType<C>>
用來提供 context 值的函式
readonly subscribe?: boolean
消費者是否要訂閱新的 context 值
ContextCallback
連結到「ContextCallback」 由 context 請求者提供的回呼,並使用滿足請求的值呼叫。
當請求的值變更時,context 提供者可以多次呼叫此回呼。
匯入:
import {type ContextCallback} from '@lit/context';
簽章:
type ContextCallback<ValueType> = (
value: ValueType,
unsubscribe?: () => void
) => void;