• Lit(四):生命周期、使用 Shadow DOM


    生命周期

    标准自定义元素生命周期

    构造函数

    创建元素时调用。

    用例:
    执行必须在第一次更新之前完成的一次性初始化任务。例如,当不使用装饰器时,可以在构造函数中设置属性的默认值,如在静态属性字段中声明属性中所示。

    constructor() {
      super();
      this.foo = 'foo';
      this.bar = 'bar';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    连接回调
    在将组件添加到文档的 DOM 时调用。

    用例:
    connectedCallback()您应该设置仅在元素连接到文档时才发生的任务。其中最常见的是将事件侦听器添加到元素外部的节点,例如添加到窗口的 keydown 事件处理程序

    connectedCallback() {
      super.connectedCallback()
      addEventListener('keydown', this._handleKeydown);
    }
    
    • 1
    • 2
    • 3
    • 4

    断开回调
    当组件从文档的 DOM 中移除时调用。
    用例:
    这个回调是向元素发出它可能不再被使用的主要信号;因此,disconnectedCallback()应该确保没有任何东西持有对元素的引用(例如添加到元素外部节点的事件侦听器),以便可以自由地进行垃圾收集。

    disconnectedCallback() {
      super.disconnectedCallback()
      window.removeEventListener('keydown', this._handleKeydown);
    }
    
    • 1
    • 2
    • 3
    • 4

    属性更改回调通过回调

    官网的描述时,一般用不到,具体见:https://lit.dev/docs/components/lifecycle/

    反应式更新周期

    当响应属性更改或requestUpdate()显式调用方法时,将触发响应更新周期。Lit 异步执行更新,因此属性更改是批处理的——如果在请求更新后但在更新开始之前有更多属性更改,则所有更改都会在同一个更新中捕获。
    更新发生在微任务时间,这意味着它们发生在浏览器将下一帧绘制到屏幕之前。

    主要掌握以下几个函数即可:

    requestUpdate: 请求更新,主动告知组件需要更新
    如果您需要在与属性无关的内容发生更改时更新和呈现元素,这将很有用。例如,计时器组件可能每秒调用requestUpdate()一次。

    connectedCallback() {
      super.connectedCallback();
      this._timerInterval = setInterval(() => this.requestUpdate(), 1000);
    }
    
    disconnectedCallback() {
      super.disconnectedCallback();
      clearInterval(this._timerInterval);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    shouldUpdate(): 调用以确定是否需要更新周期。
    如果shouldUpdate()返回true(默认情况下),则更新正常进行。如果它返回false,则不会调用更新周期的其余部分,但updateCompletePromise 仍会被解析。

    可以使用shouldUpdate()以指定哪些属性更改应导致更新

    shouldUpdate(changedProperties) {
      // 只有当属性prop1改变时更新元素
      return changedProperties.has('prop1');
    }
    
    • 1
    • 2
    • 3
    • 4

    willUpdate(): 计算依赖于其他属性并在更新过程的其余部分中使用的属性值。

    willUpdate(changedProperties) {
      // 当属性firstName或lastName改变时更新this.sha,类似于vue中的监听
      if (changedProperties.has('firstName') || changedProperties.has('lastName')) {
        this.sha = computeSHA(`${this.firstName} ${this.lastName}`);
      }
    }
    
    render() {
      return html`SHA: ${this.sha}`;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    firstUpdated(): 在组件的 DOM 第一次更新后调用

    这时dom已经可以被获取到

    firstUpdated() {
      this.renderRoot.getElementById('my-text-area').focus();
    }
    
    • 1
    • 2
    • 3

    updateComplete: 更新完成

    当updateComplete元素完成更新时,Promise 将解析。用于updateComplete等待更新。解析的值是一个布尔值,指示元素是否已完成更新。如果true更新周期结束后没有挂起的更新,则为。

    渲染完成后从组件调度事件是一种很好的做法,以便事件的侦听器看到组件的完全渲染状态。为此,您可以updateComplete在触发事件之前等待 Promise。

    async _loginClickHandler() {
      this.loggedIn = true;
      // Wait for `loggedIn` state to be rendered to the DOM
      await this.updateComplete;
      this.dispatchEvent(new Event('login'));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用 Shadow DOM

    Lit 组件使用shadow DOM来封装它们的 DOM。Shadow DOM 提供了一种向元素添加单独隔离和封装的 DOM 树的方法。DOM 封装是解锁与页面上运行的任何其他代码(包括其他 Web 组件或 Lit 组件)互操作性的关键。

    Shadow DOM 提供了三个好处:

    • DOM 范围。像这样的 DOM API document.querySelector不会在组件的影子 DOM 中找到元素,因此全局脚本更难意外破坏您的组件。
    • 样式范围。您可以为影子 DOM 编写封装样式,而不会影响 DOM 树的其余部分。
    • 组成。包含其内部 DOM 的组件的影子根与组件的子级是分开的。您可以选择如何在组件的内部 DOM 中呈现子项。

    访问Shadow DOM中的节点

    Lit 将组件渲染到它的renderRoot,默认情况下它是一个影子根。要查找内部元素,您可以使用 DOM 查询 API,例如this.renderRoot.querySelector()

    普通方式
    您可以在组件初始渲染后(例如 in firstUpdated)查询内部 DOM

      firstUpdated(): void {
        console.log(
          "组件加载完成:",
          document.getElementById("#abc"),
          this.renderRoot.querySelector("#abc")
        );
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    装饰器方式

    可以通过 @query、@queryAll、@queryAsync 访问内部组件 DOM 中的节点

    注:装饰器是一项提议的 JavaScript 功能,因此您需要使用 Babel 或 TypeScript 之类的编译器来使用装饰器。

    • @query 修改类属性,将其转换为从渲染根返回节点的 getter。可选的第二个参数为 true 时只执行一次 DOM 查询并缓存结果。
    @customElement("base-app")
    export class BaseApp extends LitElement {
      @property()
      name: string = "Lit";
    
      @query("#abc")
      _abc: any;
    
      // 渲染组件
      protected render() {
        return html` 
    你好,${this.name}
    `
    ; } firstUpdated(): void { console.log("组件加载完成:", this._abc); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这个装饰器等价于:

    get _abc() {
      return this.renderRoot?.querySelector('#abc') ?? null;
    }
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    • @queryAll,与@query 类似,只是查询全部
    • @queryAsync 类似@query,只是它不是直接返回节点,而是Promise在任何挂起的元素渲染完成后返回解析到该节点

    使用插槽渲染子级

    基本使用
    略,上一篇文章介绍样式时已经提到过了

    访问有插槽的节点

    可以创建一个 getter 来访问特定插槽的分配元素

      firstUpdated(): void {
        let a = this._slottedChildren;
      }
    
      //get 定义的方法是访问器,必须return
      get _slottedChildren() {
        const slot = this.shadowRoot.querySelector("slot");
        console.log(slot);
        console.log(slot.assignedElements({ flatten: false }));
        return slot.assignedElements({ flatten: true });
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    slotchange事件

    还可以使用该slotchange事件在分配的节点发生更改时采取措施。

    @customElement("demo-test")
    export class DemoTest extends LitElement {
      // 渲染组件
      protected render() {
        return html`
          

    获取插槽内容

    ${this.handleSlotchange}>
    `
    ; } //插槽内容改变事件 handleSlotchange(e: any) { //获取所有的插槽内容 const childNodes = e.target.assignedNodes({ flatten: true, slot: "test" }); console.log("改变:", childNodes[0].innerText); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    不知道是哪里的问题,按着文档说的应该就是当插槽内容发生改变时触发slotchange事件,结果就初始化时执行了一次,后面就不执行了
    在这里插入图片描述

    @queryAssignedElements 和 @queryAssignedNodes 装饰器

    @queryAssignedElements并将@queryAssignedNodes一个类属性转换为一个getter,该getter返回调用slot.assignedElements或slot.assignedNodes分别在组件的影子树中的给定槽上的结果。使用这些查询分配给给定槽的元素或节点。

    可选参数:

    • flatten:布尔值,指定是否通过将任何子元素替换为其分配的节点来展平分配的节点。(反正是没理解什么意思,true和false感觉也没啥区别)
    • slot:指定要查询的插槽的插槽名称。
    • selector(queryAssignedElements仅限):如果指定,则仅返回与此 CSS 选择器匹配的已分配元素。

    决定使用哪个装饰器取决于您是要查询分配给插槽的文本节点,还是只查询元素节点。

    @customElement("base-app")
    export class BaseApp extends LitElement {
      static styles?: CSSResultGroup | undefined = css`
        .odd {
          color: orange;
          font-size: 20px;
        }
        .even {
          color: blue;
          font-size: 20px;
        }
      `;
    
      // 渲染组件
      protected render() {
        return html`
          
    1
    2
    `
    ; } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    @customElement("demo-test")
    export class DemoTest extends LitElement {
      @queryAssignedElements({ flatten: true, slot: "test", selector: ".even" })
      _evenEl: any;
    
      // 渲染组件
      protected render() {
        return html`
          
    `
    ; } _getElement() { console.log("元素:", this._evenEl[0]); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    自定义渲染根

    略,没太理解是干嘛的。

  • 相关阅读:
    【CPP】指针
    如何在Asp.Net Core中注册同一接口的多个实现?
    L1-101 别再来这么多猫娘了!(2024PTA天梯赛)
    快速灵敏的 Flink1
    【SQL解析】- SQL血缘分析实现篇01
    传奇登陆游戏黑屏错位以及登陆器配置和常见问题
    Zephyr-7B论文解析及全量训练、Lora训练
    [附源码]JAVA毕业设计南京传媒学院门户网(系统+LW)
    互联网时代下的品牌传播怎么做
    element ui修改select选择框背景色和边框色
  • 原文地址:https://blog.csdn.net/weixin_41897680/article/details/126337632