1、概述
基于LitElement的组件通过响应观察到的属性更改而异步更新。
属性更改是分批进行的,如果在请求更新后,更新开始之前,发生更多属性更改,则所有更改都将捕获在同一次更新中。
在较高级别上,更新生命周期为:
- 一个属性被设置
- 检查是否需要更新。如果需要更新,发出请求。
- 执行更新:
- 通过properties 和 attributes.
- 渲染元素
- resolve一个promise,表示更新已完成
1.1 LitElement和浏览器事件循环
浏览器通过处理事件循环中的任务队列来执行JavaScript代码。在事件循环的每次迭代中,浏览器都从队列中提取任务并将其运行到完成。
任务完成后,在从队列中执行下一个任务之前,浏览器会分配时间来从其他来源(包括DOM更新,用户交互和微任务队列)执行工作。
默认情况下,LitElement更新是异步请求的,并作为微任务排队。这意味着上面的步骤3(执行更新)在事件循环的下一次迭代结束时执行。
您可以更改此行为,以便第3步在执行更新之前等待Promise。有关更多信息,请参见 performUpdate。
有关浏览器事件循环的更详细说明,请参阅Jake Archibald的文章。
1.2 生命周期回调
LitElement还从Web组件标准继承默认的生命周期回调:
connectedCallback
: 当组件被添加到document 的 DOM中时调用disconnectedCallback
: 当组件被从document 的 DOM中删除时调用adoptedCallback
: 当组件被移动到一个新的document中时调用attributeChangedCallback
: 当组件属性更改时调用
请注意,adoptedCallback 不会被 polyfilled。
所有的生命周期回调中都需要调用其父类的回调,例如
connectedCallback() { super.connectedCallback() console.log('connected') }
1.3 Promises和异步函数
LitElement使用Promise对象计划和响应元素更新。
使用async和await使得使用Promises变得容易。例如,您可以等待updateComplete承诺:
// `async` makes the function return a Promise & lets you use `await` async myFunc(data) { // Set a property, triggering an update this.myProp = data; // Wait for the updateComplete promise to resolve await this.updateComplete; // ...do stuff... return 'done'; }
由于异步函数返回Promise,因此您也可以等待它们:
let result = await myFunc('stuff'); // `result` is resolved! You can do something with it
2、函数和属性
按照调用顺序,更新生命周期中的方法和属性为:
- someProperty.hasChanged 调用属性更改
- requestUpdate 请求更新
- performUpdate 执行更新
- shouldUpdate 判断应该更新
- update 更新
- render 渲染
- firstUpdate 首次更新
- update 更新
- updateComplete 完成更新
2.1 someProperty.hasChanged
所有的声明属性都有一个hasChanged函数,只要属性被设置,它就会被调用,然后hasChanged函数返回true,才会执行更新,具体操作见前一节自定义属性更改部分
2.2 requestUpdate
//手动调用更新 this.requestUpdate(); // 从一个自定义属性的setter函数中调用 this.requestUpdate(propertyName, oldValue);
Params |
propertyName oldValue |
要更新的属性名 旧的属性值 |
Return | Promise |
返回updateComplete Promise,该Promise在更新完成时解决。 此方法内部的属性更改不会触发元素更新。 |
Update? | No |
此方法内部的属性更改不会触发元素更新。 |
如果hasChanged返回true,则将触发requestUpdate,更新将继续进行。
要手动启动元素更新,请不带任何参数调用requestUpdate。
要实现支持属性选项的自定义属性设置器,请将属性名称及其旧的的值作为参数传递。
手动属性更新的例子:
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { constructor() { super(); // Request an update in response to an event this.addEventListener('load-complete', async (e) => { console.log(e.detail.message); console.log(await this.requestUpdate()); }); } render() { return html` <button @click="${this.fire}">Fire a "load-complete" event</button> `; } fire() { let newMessage = new CustomEvent('load-complete', { detail: { message: 'hello. a load-complete happened.' } }); this.dispatchEvent(newMessage); } } customElements.define('my-element', MyElement);
在构造函数中添加自定义事件load-complete的监听器,并在点击事件中手动触发该自定义事件,调用requestUpdate函数,返回值为true
在自定义属性的setter方法中调用requestUpdate
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop: { type: Number } }; } set prop(val) { let oldVal = this._prop; this._prop = Math.floor(val); this.requestUpdate('prop', oldVal); } get prop() { return this._prop; } constructor() { super(); this._prop = 0; } render() { return html` <p>prop: ${this.prop}</p> <button @click="${() => { this.prop = Math.random()*10; }}"> change prop </button> `; } } customElements.define('my-element', MyElement);
在点击事件中给this.prop属性赋值,由于重写了prop属性的setter函数,所以会执行它,然后在setter函数中点给属性赋新的值,然后调用requestUpdate()函数,传入属性名prop和旧的值,完成属性更新
2.3 performUpdate
/** * 重写以覆盖默认行为 */ performUpdate() { ... }
Returns | void or Promise | 执行更新 |
Update? | No | 此方法内部的属性更改不会触发元素更新。 |
默认情况下,performUpdate在执行下一次浏览器事件循环之前被调用,要调用performUpdate,请将它实现为一个异步任务,并在调用super.performUpdate()之前等待一些状态
例如:
async performUpdate() { await new Promise((resolve) => requestAnimationFrame(() => resolve())); super.performUpdate(); }
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number } }; } constructor() { super(); this.prop1 = 0; } render() { return html` <p>prop1: ${this.prop1}</p> <button @click="${() => this.prop1=this.change()}">Change prop1</button> `; } async performUpdate() { console.log('Requesting animation frame...'); await new Promise((resolve) => requestAnimationFrame(() => resolve())); console.log('Got animation frame. Performing update'); super.performUpdate(); } change() { return Math.floor(Math.random()*10); } } customElements.define('my-element', MyElement);
2.4 shouldUpdate
/** * 重写以覆盖默认行为 */ shouldUpdate(changedProperties) { ... }
Params | changedProperties | map的键是已更改属性的名称;值是对应的先前值。 |
Returns | Boolean | 如果为true,则继续更新。默认返回值为true。 |
Updates? | Yes | 此方法内部的属性更改将触发元素更新。 |
控制是否应继续进行更新。实现shouldUpdate以指定哪些属性更改应引起更新。默认情况下,此方法始终返回true。
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number }, prop2: { type: Number } }; } constructor() { super(); this.prop1 = 0; this.prop2 = 0; } render() { return html` <p>prop1: ${this.prop1}</p> <p>prop2: ${this.prop2}</p> <button @click="${() => this.prop1=this.change()}">Change prop1</button> <button @click="${() => this.prop2=this.change()}">Change prop2</button> `; } /** * Only update element if prop1 changed. */ shouldUpdate(changedProperties) { changedProperties.forEach((oldValue, propName) => { console.log(`${propName} changed. oldValue: ${oldValue}`); }); return changedProperties.has('prop1'); } change() { return Math.floor(Math.random()*10); } } customElements.define('my-element', MyElement);
由于在shouldUpdate方法中总是判断键是否为prop1,所以prop2的属性更改总是在最后一步被终止
2.5 update
Params | changedProperties | map的键是已更改属性的名称;值是对应的先前值。 |
Updates? | No | 此方法内部的属性更改不会触发元素更新。 |
将property值反射为attributes属性,并通过lit-html调用render来渲染DOM。在此提供参考。您无需覆盖或调用此方法。
2.6 render
/** * 重写render以覆盖默认行为 */ render() { ... }
Returns | TemplateResult | 必须返回lit-html TemplateResult |
Updates? | No | 此方法内部的属性更改不会触发元素更新。 |
使用lit-html渲染元素模板。您必须为扩展LitElement基类的任何组件实现render函数
2.7 firstUpdated
/** * 重写 */ firstUpdated(changedProperties) { ... }
Params | changedProperties | map的键是已更改属性的名称;值是相应的先前值。 |
Updates? | Yes | 此方法内部的属性更改将触发元素更新。 |
在元素的DOM第一次更新之后,即在调用更新之前立即调用
创建元素模板后,请重写firstUpdated以执行一次性工作。
Example:在第一次输入时将光标焦点指向输入框
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { textAreaId: { type: String }, startingText: { type: String } }; } constructor() { super(); this.textAreaId = 'myText'; this.startingText = 'Focus me on first update'; } render() { return html` <textarea id="${this.textAreaId}">${this.startingText}</textarea> `; } firstUpdated(changedProperties) { changedProperties.forEach((oldValue, propName) => { console.log(`${propName} changed. oldValue: ${oldValue}`); }); const textArea = this.shadowRoot.getElementById(this.textAreaId); textArea.focus(); } } customElements.define('my-element', MyElement);
2.8 updated
/** * 重写... */ updated(changedProperties) { ... }
Params | changedProperties | map的键是已更改属性的名称;值是对应的先前值。 |
Updates? | Yes | 此方法内部的属性更改将触发元素更新。 |
在元素的DOM已更新和呈现时调用。重写以在更新后执行某些任务。
example:在更新后将焦点聚集到元素上
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number }, prop2: { type: Number } }; } constructor() { super(); this.prop1 = 0; this.prop2 = 0; } render() { return html` <style>button:focus { background-color: aliceblue; }</style> <p>prop1: ${this.prop1}</p> <p>prop2: ${this.prop2}</p> <button id="a" @click="${() => this.prop1=Math.random()}">prop1</button> <button id="b" @click="${() => this.prop2=Math.random()}">prop2</button> `; } updated(changedProperties) { changedProperties.forEach((oldValue, propName) => { console.log(`${propName} changed. oldValue: ${oldValue}`); }); let b = this.shadowRoot.getElementById('b'); b.focus(); } } customElements.define('my-element', MyElement);
每次更新后都会聚焦于#b元素
2.9 updateComplete
// Await Promise property. await this.updateComplete;
Type | Promise | 当元素完成更新时返回一个布尔值 |
Resolves |
如果没有其他待处理的更新则返回true 如果此更新周期触发了另一个更新则返回false |
当元素完成更新时updateComplete
Promise resolves,使用updateComplete等待更新:
await this.updateComplete; // do stuff
this.updateComplete.then(() => { /* do stuff */ });
example:
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number } }; } constructor() { super(); this.prop1 = 0; } render() { return html` <p>prop1: ${this.prop1}</p> <button @click="${this.changeProp}">prop1</button> `; } async getMoreState() { return; } async changeProp() { this.prop1 = Math.random(); await Promise.all(this.updateComplete, this.getMoreState()); console.log('Update complete. Other state completed.'); } } customElements.define('my-element', MyElement);
2.10 重写updateComplete
要在实现updateComplete承诺之前等待其他状态,请重写_getUpdateComplete方法。例如,在这里等待子元素的更新可能很有用。首先等待super._getUpdateComplete(),然后等待任何后续状态。
建议覆盖_getUpdateComplete方法而不是updateComplete getter,以确保与使用TypeScript的ES5输出的用户兼容(请参阅TypeScript#338)。
class MyElement extends LitElement { async _getUpdateComplete() { await super._getUpdateComplete(); await this._myChild.updateComplete; } }
3、Examples
3.1 控制何时处理更新
重写 performUpdate方法
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number } }; } constructor() { super(); this.prop1 = 0; } render() { return html` <p>prop1: ${this.prop1}</p> <button @click="${() => this.prop1=this.change()}">Change prop1</button> `; } async performUpdate() { console.log('Requesting animation frame...'); await new Promise((resolve) => requestAnimationFrame(() => resolve())); console.log('Got animation frame. Performing update'); super.performUpdate(); } change() { return Math.floor(Math.random()*10); } } customElements.define('my-element', MyElement);
3.2 自定义哪些属性更改应引起更新
重写shouldUpdate
方法
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number }, prop2: { type: Number } }; } constructor() { super(); this.prop1 = 0; this.prop2 = 0; } render() { return html` <p>prop1: ${this.prop1}</p> <p>prop2: ${this.prop2}</p> <button @click="${() => this.prop1=this.change()}">Change prop1</button> <button @click="${() => this.prop2=this.change()}">Change prop2</button> `; } /** * Only update element if prop1 changed. */ shouldUpdate(changedProperties) { changedProperties.forEach((oldValue, propName) => { console.log(`${propName} changed. oldValue: ${oldValue}`); }); return changedProperties.has('prop1'); } change() { return Math.floor(Math.random()*10); } } customElements.define('my-element', MyElement);
3.3 自定义一组属性更改
为属性指定hasChanged。请参阅属性文档。
3.4 管理对象子属性的属性更改和更新
不能观察到变动(对对象子属性和数组项的更改)。而是重写整个对象,或在发生变动后调用requestUpdate。
// Option 1: Rewrite whole object, triggering an update this.prop1 = Object.assign({}, this.prop1, { subProp: 'data' }); // Option 2: Mutate a subproperty, then call requestUpdate this.prop1.subProp = 'data'; this.requestUpdate();
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Object } }; } constructor() { super(); this.prop1 = { subProp: 0 } } render() { return html` <p>prop1.subProp: ${this.prop1.subProp}</p> <button @click="${this.change}">change</button> `; } change() { let newVal = Math.random(); /** * Changes to object subproperties and array items are not observable. * Instead: */ // Option 1: Rewrite the whole object, triggering an update // this.prop1 = Object.assign({}, this.prop1, { subProp: newVal }); // Option 2: Mutate a subproperty, then call requestUpdate this.prop1.subProp = newVal; this.requestUpdate(); } } customElements.define('my-element', MyElement);
3.5 在响应中更新非属性更改
调用requestUpdate
:
// Request an update in response to an event this.addEventListener('load-complete', async (e) => { console.log(e.detail.message); console.log(await this.requestUpdate()); });
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { constructor() { super(); // Request an update in response to an event this.addEventListener('load-complete', async (e) => { console.log(e.detail.message); console.log(await this.requestUpdate()); }); } render() { return html` <button @click="${this.fire}">Fire a "load-complete" event</button> `; } fire() { let newMessage = new CustomEvent('load-complete', { detail: { message: 'hello. a load-complete happened.' } }); this.dispatchEvent(newMessage); } } customElements.define('my-element', MyElement);
3.6 无论属性更改如何,都请求更新
调用requestUpdate
:
this.requestUpdate();
3.7 请求更正指定的属性
调用requestUpdate(propName, oldValue)
:
let oldValue = this.prop1; this.prop1 = 'new value'; this.requestUpdate('prop1', oldValue);
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { constructor() { super(); // Request an update in response to an event this.addEventListener('load-complete', async (e) => { console.log(e.detail.message); console.log(await this.requestUpdate()); }); } render() { return html` <button @click="${this.fire}">Fire a "load-complete" event</button> `; } fire() { let newMessage = new CustomEvent('load-complete', { detail: { message: 'hello. a load-complete happened.' } }); this.dispatchEvent(newMessage); } } customElements.define('my-element', MyElement);
3.8 第一次更新后做点什么
重写 firstUpdated
:
firstUpdated(changedProps) { console.log(changedProps.get('prop1')); }
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { textAreaId: { type: String }, startingText: { type: String } }; } constructor() { super(); this.textAreaId = 'myText'; this.startingText = 'Focus me on first update'; } render() { return html` <textarea id="${this.textAreaId}">${this.startingText}</textarea> `; } firstUpdated(changedProperties) { changedProperties.forEach((oldValue, propName) => { console.log(`${propName} changed. oldValue: ${oldValue}`); }); const textArea = this.shadowRoot.getElementById(this.textAreaId); textArea.focus(); } } customElements.define('my-element', MyElement);
3.9 每次更新后都做些事情
重写 updated
:
updated(changedProps) { console.log(changedProps.get('prop1')); }
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number }, prop2: { type: Number } }; } constructor() { super(); this.prop1 = 0; this.prop2 = 0; } render() { return html` <style>button:focus { background-color: aliceblue; }</style> <p>prop1: ${this.prop1}</p> <p>prop2: ${this.prop2}</p> <button id="a" @click="${() => this.prop1=Math.random()}">prop1</button> <button id="b" @click="${() => this.prop2=Math.random()}">prop2</button> `; } updated(changedProperties) { changedProperties.forEach((oldValue, propName) => { console.log(`${propName} changed. oldValue: ${oldValue}`); }); let b = this.shadowRoot.getElementById('b'); b.focus(); } } customElements.define('my-element', MyElement);
3.10 元素下次更新时执行一些操作
等待updateComplete承诺:
await this.updateComplete; // do stuff
this.updateComplete.then(() => { // do stuff });
3.11 等待元素完成更新
等待updateComplete承诺:
let done = await updateComplete;
updateComplete.then(() => { // finished updating });
import { LitElement, html } from 'lit-element'; class MyElement extends LitElement { static get properties() { return { prop1: { type: Number } }; } constructor() { super(); this.prop1 = 0; } render() { return html` <p>prop1: ${this.prop1}</p> <button @click="${this.changeProp}">prop1</button> `; } async getMoreState() { return; } async changeProp() { this.prop1 = Math.random(); await Promise.all(this.updateComplete, this.getMoreState()); console.log('Update complete. Other state completed.'); } } customElements.define('my-element', MyElement);
Lit-Element基本用法完