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

生命周期

标准自定义元素生命周期

构造函数

创建元素时调用。

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

constructor() {
    
    
  super();
  this.foo = 'foo';
  this.bar = 'bar';
}

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

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

connectedCallback() {
    
    
  super.connectedCallback()
  addEventListener('keydown', this._handleKeydown);
}

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

disconnectedCallback() {
    
    
  super.disconnectedCallback()
  window.removeEventListener('keydown', this._handleKeydown);
}

属性更改回调通过回调

官网的描述时,一般用不到,具体见: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);
}

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

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

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

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}`;
}

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

这时dom已经可以被获取到

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

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'));
}

使用 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")
    );
  }

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

可以通过 @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` <div id="abc">你好,${
      
      this.name}</div> `;
  }

  firstUpdated(): void {
    
    
    console.log("组件加载完成:", this._abc);
  }
}

这个装饰器等价于:

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

在这里插入图片描述

  • @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 });
  }

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

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

@customElement("demo-test")
export class DemoTest extends LitElement {
    
    
  // 渲染组件
  protected render() {
    
    
    return html`
      <div>
        <p>获取插槽内容</p>
        <slot name="test" @slotchange=${
      
      this.handleSlotchange}></slot>
      </div>
    `;
  }

  //插槽内容改变事件
  handleSlotchange(e: any) {
    
    
    //获取所有的插槽内容
    const childNodes = e.target.assignedNodes({
    
     flatten: true, slot: "test" });
    console.log("改变:", childNodes[0].innerText);
  }
}

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

@queryAssignedElements 和 @queryAssignedNodes 装饰器

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

可选参数:

  • flatten:布尔值,指定是否通过将任何子<slot>元素替换为其分配的节点来展平分配的节点。(反正是没理解什么意思,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`
      <div>
        <demo-test>
          <div slot="test" class="odd">1</div>
          <div slot="test" class="even">2</div>
        </demo-test>
      </div>
    `;
  }
}
@customElement("demo-test")
export class DemoTest extends LitElement {
    
    
  @queryAssignedElements({
    
     flatten: true, slot: "test", selector: ".even" })
  _evenEl: any;

  // 渲染组件
  protected render() {
    
    
    return html`
      <div>
        <button @click=${
      
      this._getElement}>获取元素</button>
        <slot name="test"></slot>
      </div>
    `;
  }

  _getElement() {
    
    
    console.log("元素:", this._evenEl[0]);
  }
}

在这里插入图片描述

自定义渲染根

略,没太理解是干嘛的。

猜你喜欢

转载自blog.csdn.net/weixin_41897680/article/details/126337632