• LitElement(六)生命周期


    1、概述

    基于LitElement的组件通过响应观察到的属性更改而异步更新。

    属性更改是分批进行的,如果在请求更新后,更新开始之前,发生更多属性更改,则所有更改都将捕获在同一次更新中。

    在较高级别上,更新生命周期为:

    1. 一个属性被设置
    2. 检查是否需要更新。如果需要更新,发出请求。
    3. 执行更新:
      • 通过properties 和 attributes.
      • 渲染元素
    4. 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对象计划和响应元素更新。

    使用asyncawait使得使用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、函数和属性

    按照调用顺序,更新生命周期中的方法和属性为:

    1. someProperty.hasChanged 调用属性更改
    2. requestUpdate 请求更新
    3. performUpdate 执行更新
    4. shouldUpdate 判断应该更新
    5. update 更新
    6. render 渲染
    7. firstUpdate 首次更新
    8. update 更新
    9. 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基本用法完

  • 相关阅读:
    Java实现 LeetCode 657 机器人能否返回原点(暴力大法)
    PHP imagearc
    PHP imageantialias
    PHP imagealphablending
    PHP imageaffinematrixget
    PHP imageaffinematrixconcat
    空单元 | empty-cells (Miscellaneous Level 2)
    矩阵 | matrix() (Transforms)
    相邻兄弟选择器 | Adjacent sibling selectors (Selectors)
    相抵路径 | offset-path (Motion Path)
  • 原文地址:https://www.cnblogs.com/jixiaohua/p/12004060.html
Copyright © 2020-2023  润新知