響應式屬性
Lit 元件接收輸入並將其狀態儲存為 JavaScript 類別欄位或屬性。響應式屬性是在變更時可以觸發響應式更新週期的屬性,重新渲染元件,並且可以選擇性地讀取或寫入屬性。
class MyElement extends LitElement {
@property()
name?: string;
}
class MyElement extends LitElement {
static properties = {
name: {},
};
}
Lit 管理您的響應式屬性及其對應的屬性。特別是
- 響應式更新。Lit 為每個響應式屬性產生一個 getter/setter 配對。當響應式屬性變更時,元件會排程更新。
- 屬性處理。預設情況下,Lit 會設定與屬性對應的觀察屬性,並在屬性變更時更新屬性。屬性值也可以選擇性地反映回屬性。
- 超類別屬性。Lit 會自動套用超類別宣告的屬性選項。除非您想變更選項,否則不需要重新宣告屬性。
- 元件升級。如果 Lit 元件是在元件已經在 DOM 中之後定義的,Lit 會處理升級邏輯,確保在元件升級之前在元件上設定的任何屬性在元件升級時都會觸發正確的響應式副作用。
公開屬性和內部狀態
「公開屬性和內部狀態」的永久連結公開屬性是元件公開 API 的一部分。一般而言,公開屬性 (尤其是公開響應式屬性) 應被視為輸入。
元件不應變更自己的公開屬性,除非是為了回應使用者輸入。例如,選單元件可能有一個公開的 selected
屬性,可以由元件的所有者初始化為給定的值,但當使用者選擇項目時,由元件本身更新。在這些情況下,元件應發送一個事件,以向元件的所有者指示 selected
屬性已變更。有關更多詳細資訊,請參閱發送事件。
Lit 也支援內部響應式狀態。內部響應式狀態是指不屬於元件 API 的響應式屬性。這些屬性沒有對應的屬性,並且通常在 TypeScript 中標記為受保護或私有的。
@state()
private _counter = 0;
static properties = {
_counter: {state: true}
};
constructor() {
super();
this._counter = 0;
}
元件會操作自己的內部響應式狀態。在某些情況下,內部響應式狀態可能會從公開屬性初始化,例如,如果使用者可見屬性和內部狀態之間存在昂貴的轉換。
與公開響應式屬性一樣,更新內部響應式狀態會觸發更新週期。有關更多資訊,請參閱內部響應式狀態。
公開響應式屬性
「公開響應式屬性」的永久連結使用裝飾器或靜態 properties
欄位宣告元件的公開響應式屬性。
在這兩種情況下,您都可以傳遞選項物件來設定屬性的功能。
使用裝飾器宣告屬性
「使用裝飾器宣告屬性」的永久連結將 @property
裝飾器與類別欄位宣告搭配使用,以宣告響應式屬性。
class MyElement extends LitElement {
@property({type: String})
mode?: string;
@property({attribute: false})
data = {};
}
@property
裝飾器的引數是選項物件。省略引數相當於為所有選項指定預設值。
使用裝飾器。裝飾器是提議的 JavaScript 功能,因此您需要使用 Babel 或 TypeScript 編譯器等編譯器才能使用裝飾器。有關詳細資訊,請參閱啟用裝飾器。
在靜態屬性類別欄位中宣告屬性
「在靜態屬性類別欄位中宣告屬性」的永久連結若要在靜態 properties
類別欄位中宣告屬性
class MyElement extends LitElement {
static properties = {
mode: {type: String},
data: {attribute: false},
};
constructor() {
super();
this.data = {};
}
}
空的選項物件相當於為所有選項指定預設值。
避免宣告屬性時類別欄位造成的問題
「避免宣告屬性時類別欄位造成的問題」的永久連結類別欄位與響應式屬性有互動問題。類別欄位是在元件執行個體上定義的,而響應式屬性是在元件原型上定義為存取器。根據 JavaScript 的規則,執行個體屬性的優先順序高於原型屬性,並且會有效地隱藏原型屬性。這表示當使用類別欄位時,響應式屬性存取器無法運作,這樣設定屬性就不會觸發元件更新。
class MyElement extends LitElement {
static properties = {foo: {type: String}}
foo = 'Default'; // ❌ this will make `foo` not reactive
}
在 JavaScript 中,宣告響應式屬性時,您不得使用類別欄位。相反地,屬性必須在元件建構函式中初始化
class MyElement extends LitElement {
static properties = {foo: {type: String}}
constructor() {
super();
this.foo = 'Default';
}
}
或者,您可以使用Babel 的標準裝飾器來宣告響應式屬性。
class MyElement extends LitElement {
@property()
accessor foo = 'Default';
}
對於 TypeScript,您可以使用類別欄位來宣告響應式屬性,只要您使用以下模式之一
- 將
useDefineForClassFields
編譯器選項設定為false
。當將裝飾器與 TypeScript 搭配使用時,這已是建議作法。
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true, // If using decorators
"useDefineForClassFields": false,
}
}
class MyElement extends LitElement {
static properties = {foo: {type: String}}
foo = 'Default';
@property()
bar = 'Default';
}
- 在欄位上新增
declare
關鍵字,並將欄位的初始值設定式放在建構函式中。
class MyElement extends LitElement {
declare foo: string;
static properties = {foo: {type: String}}
constructor() {
super();
this.foo = 'Default';
}
}
- 在欄位上新增
accessor
關鍵字以使用自動存取器。
class MyElement extends LitElement {
static properties = {foo: {type: String}}
accessor foo = 'Default';
@property()
accessor bar = 'Default';
}
屬性選項
「屬性選項」的永久連結選項物件可以具有以下屬性
attribute
屬性是否與屬性相關聯,或是相關聯屬性的自訂名稱。預設值:true。如果
attribute
為 false,則會忽略converter
、reflect
和type
選項。有關更多資訊,請參閱設定屬性名稱。converter
hasChanged
在每次設定屬性時呼叫的函式,以判斷屬性是否已變更,並且是否應觸發更新。如果未指定,LitElement 會使用嚴格的不等式檢查 (
newValue !== oldValue
) 來判斷屬性值是否已變更。有關更多資訊,請參閱自訂變更偵測。noAccessor
設定為 true 可避免產生預設的屬性存取器。這個選項很少有必要。預設值:false。有關更多資訊,請參閱防止 Lit 產生屬性存取器。
reflect
屬性值是否反映回相關聯的屬性。預設值:false。有關更多資訊,請參閱啟用屬性反映。
state
設定為 true 可將屬性宣告為內部響應式狀態。內部響應式狀態會像公開響應式屬性一樣觸發更新,但 Lit 不會為其產生屬性,而且使用者不應從元件外部存取它。相當於使用
@state
裝飾器。預設值:false。有關更多資訊,請參閱內部響應式狀態。type
在將字串值的屬性轉換為屬性時,Lit 的預設屬性轉換器會將字串剖析為指定的類型,反之亦然 (在將屬性反映為屬性時)。如果設定了
converter
,則此欄位會傳遞給轉換器。如果未指定type
,則預設轉換器會將其視為type: String
。請參閱使用預設轉換器。當使用 TypeScript 時,此欄位通常應與為該欄位宣告的 TypeScript 類型相符。但是,
type
選項是由 Lit 的執行階段用於字串序列化/還原序列化,不應與類型檢查機制混淆。- 呼叫屬性的設定器。
- 設定器會呼叫元件的
requestUpdate
方法。 - 比較屬性的舊值和新值。
- 預設情況下,Lit 使用嚴格的不等式測試來判斷值是否已變更 (即
newValue !== oldValue
)。 - 如果屬性具有
hasChanged
函式,則會以屬性的舊值和新值呼叫該函式。
- 預設情況下,Lit 使用嚴格的不等式測試來判斷值是否已變更 (即
- 如果偵測到屬性變更,則會以非同步方式排程更新。如果已排程更新,則只會執行單次更新。
- 呼叫元件的
update
方法,將已變更的屬性反映到特性,並重新渲染元件的範本。 不可變資料模式。 將物件和陣列視為不可變。例如,若要從
myArray
中移除項目,請建構新的陣列this.myArray = this.myArray.filter((_, i) => i !== indexToRemove);
雖然此範例很簡單,但使用像Immer這樣的程式庫來管理不可變資料通常會很有幫助。這有助於避免在設定深度巢狀物件時產生棘手的樣板程式碼。
手動觸發更新。 修改資料並呼叫
requestUpdate()
來直接觸發更新。例如this.myArray.splice(indexToRemove, 1);
this.requestUpdate();
在未帶任何引數的情況下呼叫時,
requestUpdate()
會排程更新,而不會呼叫hasChanged()
函式。但是請注意,requestUpdate()
只會導致目前的元件更新。也就是說,如果某個元件使用上面顯示的程式碼,而且該元件將this.myArray
傳遞給子元件,則子元件會偵測到陣列參考並未變更,因此它不會更新。若要觀察特性 (從特性設定屬性),特性值必須從字串轉換為符合屬性類型。
若要反映特性 (從屬性設定特性),屬性值必須轉換為字串。
變更屬性名稱,使其預設為 false。例如,Web 平台使用
disabled
特性 (預設為 false),而不是enabled
。改用字串值或數值特性。
省略選項物件或指定空的選項物件相當於為所有選項指定預設值。
內部響應式狀態
「內部響應式狀態」的永久連結內部響應式狀態是指不屬於元件公開 API 的響應式屬性。這些狀態屬性沒有對應的屬性,並且不打算從元件外部使用。內部響應式狀態應由元件本身設定。
使用 @state
裝飾器宣告內部響應式狀態
@state()
protected _active = false;
使用靜態 properties
類別欄位,您可以使用 state: true
選項來宣告內部響應式狀態。
static properties = {
_active: {state: true}
};
constructor() {
this._active = false;
}
不應從元件外部參考內部響應式狀態。在 TypeScript 中,這些屬性應標記為私有或受保護。我們也建議使用類似底線 (_
) 開頭的慣例,以識別 JavaScript 使用者的私有或受保護屬性。
內部響應式狀態的工作方式與公開的響應式屬性類似,只是該屬性沒有關聯的特性。您可以為內部響應式狀態指定的唯一選項是 hasChanged
函式。
@state
裝飾器也可以作為程式碼縮減器的提示,表示屬性名稱可以在縮減期間變更。
當屬性變更時會發生什麼
“屬性變更時會發生什麼事”的永久連結屬性變更可能會觸發響應式更新週期,導致元件重新渲染其範本。
當屬性變更時,會發生以下順序
請注意,如果您修改物件或陣列屬性,它不會觸發更新,因為物件本身並未變更。如需更多資訊,請參閱修改物件和陣列屬性。
有多種方法可以掛鉤並修改響應式更新週期。如需更多資訊,請參閱響應式更新週期。
如需更多關於屬性變更偵測的資訊,請參閱自訂變更偵測。
變更物件和陣列屬性
“修改物件和陣列屬性”的永久連結修改物件或陣列不會變更物件參考,因此它不會觸發更新。您可以透過兩種方式處理物件和陣列屬性
一般而言,對於大多數應用程式來說,使用由上而下的資料流程和不可變物件是最好的做法。 它可確保每個需要渲染新值的元件都執行 (並且盡可能有效率地執行,因為資料樹中未變更的部分不會導致依賴它們的元件更新)。
直接修改資料並呼叫 requestUpdate()
應視為進階使用案例。在這種情況下,您 (或其他某個系統) 需要識別所有使用修改資料的元件,並在每個元件上呼叫 requestUpdate()
。當這些元件分散在應用程式中時,這會變得難以管理。如果不這樣做,表示您可能會修改在應用程式的兩個部分中渲染的物件,但只會更新其中一個部分。
在簡單的情況下,如果您知道給定的資料只會在單一元件中使用,那麼修改資料並呼叫 requestUpdate()
應該是安全的,如果您喜歡的話。
雖然屬性非常適合接收 JavaScript 資料作為輸入,但特性是 HTML 允許從標記設定元素的標準方式,而無需使用 JavaScript 來設定屬性。為其響應式屬性提供屬性和特性介面是 Lit 元件可以在各種環境中發揮作用的關鍵方式,包括那些在沒有用戶端範本引擎的情況下渲染的環境,例如從 CMS 提供的靜態 HTML 頁面。
預設情況下,Lit 會為每個公開的響應式屬性設定對應的觀察特性,並在特性變更時更新屬性。屬性值也可以選擇性地反映 (寫回特性)。
雖然元素屬性可以是任何類型,但特性始終是字串。這會影響非字串屬性的觀察特性和反映特性
公開特性的布林屬性應預設為 false。如需更多資訊,請參閱布林特性。
設定屬性名稱
“設定特性名稱”的永久連結預設情況下,Lit 會為所有公開的響應式屬性建立對應的觀察特性。觀察特性的名稱是屬性名稱,以小寫形式呈現
// observed attribute name is "myvalue"
@property({ type: Number })
myValue = 0;
// observed attribute name is "myvalue"
static properties = {
myValue: { type: Number },
};
constructor() {
super();
this.myValue = 0;
}
若要建立具有不同名稱的觀察特性,請將 attribute
設定為字串
// Observed attribute will be called my-name
@property({ attribute: 'my-name' })
myName = 'Ogden';
// Observed attribute will be called my-name
static properties = {
myName: { attribute: 'my-name' },
};
constructor() {
super();
this.myName = 'Ogden'
}
若要防止為屬性建立觀察特性,請將 attribute
設定為 false
。屬性不會從標記中的特性初始化,且特性變更不會影響它。
// No observed attribute for this property
@property({ attribute: false })
myData = {};
// No observed attribute for this property
static properties = {
myData: { attribute: false },
};
constructor() {
super();
this.myData = {};
}
內部響應式狀態永遠沒有關聯的特性。
觀察特性可用於從標記為屬性提供初始值。例如
<my-element myvalue="99"></my-element>
使用預設轉換器
“使用預設轉換器”的永久連結Lit 具有可處理 String
、Number
、Boolean
、Array
和 Object
屬性類型的預設轉換器。
若要使用預設轉換器,請在屬性宣告中指定 type
選項
// Use the default converter
@property({ type: Number })
count = 0;
// Use the default converter
static properties = {
count: { type: Number },
};
constructor() {
super();
this.count = 0;
}
如果您未為屬性指定類型或自訂轉換器,它的行為會如同您已指定 type: String
。
下表顯示預設轉換器如何處理每種類型的轉換。
從特性到屬性
類型 | 轉換 |
---|---|
字串 | 如果元素具有對應的特性,請將屬性設定為特性值。 |
數字 | 如果元素具有對應的特性,請將屬性設定為 Number(attributeValue) 。 |
布林 | 如果元素具有對應的特性,請將屬性設定為 true。 否則,請將屬性設定為 false。 |
Object , Array | 如果元素具有對應的特性,請將屬性值設定為 JSON.parse(attributeValue) 。 |
對於除 Boolean
以外的任何情況,如果元素沒有對應的特性,則屬性會保留其預設值,或者如果未設定預設值,則會保留為 undefined
。
從屬性到特性
類型 | 轉換 |
---|---|
String , Number | 如果屬性已定義且不為 null,請將特性設定為屬性值。 如果屬性為 null 或 undefined,請移除特性。 |
布林 | 如果屬性為真值,請建立特性並將其值設定為空字串。 如果屬性為假值,請移除特性 |
Object , Array | 如果屬性已定義且不為 null,請將特性設定為 JSON.stringify(propertyValue) 。如果屬性為 null 或 undefined,請移除特性。 |
提供自訂轉換器
“提供自訂轉換器”的永久連結您可以使用 converter
選項在屬性宣告中指定自訂屬性轉換器
myProp: {
converter: // Custom property converter
}
converter
可以是物件或函式。如果是物件,它可以具有 fromAttribute
和 toAttribute
的索引鍵
prop1: {
converter: {
fromAttribute: (value, type) => {
// `value` is a string
// Convert it to a value of type `type` and return it
},
toAttribute: (value, type) => {
// `value` is of type `type`
// Convert it to a string and return it
}
}
}
如果 converter
是函式,則它會取代 fromAttribute
使用
myProp: {
converter: (value, type) => {
// `value` is a string
// Convert it to a value of type `type` and return it
}
}
如果未為反映的特性提供 toAttribute
函式,則會使用預設轉換器將特性設定為屬性值。
如果 toAttribute
傳回 null
或 undefined
,則會移除特性。
布林屬性
“布林特性”的永久連結若要讓布林屬性可從特性設定,它必須預設為 false。如果它預設為 true,您就無法從標記將其設定為 false,因為特性的存在 (無論是否有值) 都等同於 true。這是 Web 平台中特性的標準行為。
如果此行為不符合您的使用案例,則有幾個選項
啟用屬性反映
“啟用特性反映”的永久連結您可以設定屬性,使其在變更時,其值會反映到其對應的特性。反映的特性很有用,因為特性對於 CSS 可見,且對於 DOM API (例如 querySelector
) 可見。
例如
// Value of property "active" will reflect to attribute "active"
active: {reflect: true}
當屬性變更時,Lit 會如使用預設轉換器或提供自訂轉換器中所述設定對應的特性值。
特性通常應被視為元素從其擁有者接收的輸入,而不是由元素本身控制,因此應謹慎地將屬性反映到特性。對於樣式和協助工具等案例而言,這在今天是必要的,但隨著平台新增:state
虛擬選取器和協助工具物件模型等功能,以填補這些空白,這種情況可能會發生變化。
不建議反映物件或陣列類型的屬性。這可能會導致大型物件序列化至 DOM,進而導致效能不佳。
Lit 會在更新期間追蹤反映狀態。 您可能已經意識到,如果將屬性變更反映到特性,而特性變更會更新屬性,則它可能會建立無限迴圈。但是,Lit 會追蹤屬性和特性何時設定,以明確防止這種情況發生
自訂屬性存取器
“自訂屬性存取器”的永久連結預設情況下,LitElement 會為所有響應式屬性產生 getter/setter 配對。每當您設定屬性時,都會呼叫 setter
// Declare a property
@property()
greeting: string = 'Hello';
...
// Later, set the property
this.greeting = 'Hola'; // invokes greeting's generated property accessor
// Declare a property
static properties = {
greeting: {},
}
constructor() {
this.super();
this.greeting = 'Hello';
}
...
// Later, set the property
this.greeting = 'Hola'; // invokes greeting's generated property accessor
自動產生的存取器會自動呼叫 requestUpdate()
,如果尚未開始更新,則會啟動更新。
建立自訂屬性存取器
到「建立自訂屬性存取器」的永久連結若要指定屬性的取值和設值方式,您可以定義自己的 getter/setter 配對。例如:
private _prop = 0;
@property()
set prop(val: number) {
this._prop = Math.floor(val);
}
get prop() { return this._prop; }
static properties = {
prop: {},
};
_prop = 0;
set prop(val) {
this._prop = Math.floor(val);
}
get prop() { return this._prop; }
若要將自訂屬性存取器與 @property
或 @state
裝飾器搭配使用,請將裝飾器放在 setter 上,如上所示。經過 @property
或 @state
裝飾的 setter 會呼叫 requestUpdate()
。
在大多數情況下,您不需要建立自訂屬性存取器。 若要從現有屬性計算值,我們建議使用 willUpdate
回呼,這樣您可以在更新週期期間設定值,而不會觸發額外的更新。若要在元素更新後執行自訂動作,我們建議使用 updated
回呼。在極少數情況下,當需要同步驗證使用者設定的任何值時,可以使用自訂 setter。
如果您的類別為屬性定義了自己的存取器,Lit 將不會以產生的存取器覆寫它們。如果您的類別沒有為屬性定義存取器,即使父類別已定義該屬性或存取器,Lit 也會產生它們。
防止 Lit 產生屬性存取器
到「防止 Lit 產生屬性存取器」的永久連結在極少數情況下,子類別可能需要變更或新增父類別上存在的屬性的屬性選項。
為了防止 Lit 產生覆寫父類別已定義存取器的屬性存取器,請在屬性宣告中將 noAccessor
設定為 true
。
static properties = {
myProp: { type: Number, noAccessor: true }
};
當您定義自己的存取器時,不需要設定 noAccessor
。
自訂變更偵測
到「自訂變更偵測」的永久連結所有響應式屬性都有一個函式 hasChanged()
,會在設定屬性時被呼叫。
hasChanged
會比較屬性的舊值和新值,並評估屬性是否已變更。如果 hasChanged()
傳回 true,則 Lit 會啟動元素更新(如果尚未排程)。有關更新的更多資訊,請參閱響應式更新週期。
hasChanged()
的預設實作使用嚴格的不等式比較:如果 newVal !== oldVal
,則 hasChanged()
會傳回 true
。
若要自訂屬性的 hasChanged()
,請將其指定為屬性選項。
@property({
hasChanged(newVal: string, oldVal: string) {
return newVal?.toLowerCase() !== oldVal?.toLowerCase();
}
})
myProp: string | undefined;
static properties = {
myProp: {
hasChanged(newVal, oldVal) {
return newVal?.toLowerCase() !== oldVal?.toLowerCase();
}
}
};
在以下範例中,hasChanged()
僅在值為奇數時傳回 true。