Lit(六):内置指令、自定义指令

内置指令

样式

classMap

将类列表设置为基于对象的元素

import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  static styles = css`
    .fontRed {
      color: red;
      font-size: 30px;
    }

    .fontFamily {
      font-family: "楷体";
    }
  `;
  // 渲染组件
  protected render() {
    return html`
      <div class=${classMap({ fontRed: true })}>基本用法</div>
      <div class="fontFamily ${classMap({ fontRed: true })}">混合用法</div>
    `;
  }
}

在这里插入图片描述

styleMap
将样式属性列表设置为基于对象的元素。

import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { styleMap } from "lit/directives/style-map.js";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  myStyle = {
    fontSize: "20px",
  };

  // 渲染组件
  protected render() {
    return html`
      <div style="${styleMap({ color: "red", fontSize: "20px" })}">
        基本用法
      </div>
      <div style="color:blue;${styleMap(this.myStyle)}">混合用法</div>
    `;
  }
}

在这里插入图片描述

循环和条件

when

import {when} from 'lit/directives/when.js';

when<T, F>(
  condition: boolean,
  trueCase: () => T,
  falseCase?: () => F
)

condition为真时,返回调用的结果trueCase(),否则返回调用falseCase()的结果falseCase。
这是一个围绕三元表达式的便捷包装器。

import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { when } from "lit/directives/when.js";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  private userInfo = {
    name: "张三",
  };

  // 渲染组件
  protected render() {
    return html`
      <!-- 当userInfo对象存在时,显示名称,否则显示请登录 -->
      ${when(
        this.userInfo,
        () => html`<div>${this.userInfo.name}</div>`,
        () => html`<div>请登录</div>`
      )}
    `;
  }
}

在这里插入图片描述
choose

import {choose} from 'lit/directives/choose.js';

choose<T, V>(
  value: T,
  cases: Array<[T, () => V]>,
  defaultCase?: () => V
)

案例的结构为[caseValue, func]。由严格相等value来匹配。caseValue第一个匹配被选中。案例值可以是任何类型,包括原语、对象和符号。
这类似于 switch 语句,但作为一个表达式并且没有失败。

import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { choose } from "lit/directives/choose.js";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  private selection = "home";

  // 渲染组件
  protected render() {
    return html`
      ${choose(
        this.selection,
        [
          ["home", () => html`<div>首页</div>`],
          ["about", () => html`<div>关于</div>`],
        ],
        () => html`<div>请登录</div>`
      )}
    `;
  }
}

在这里插入图片描述

map

import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { map } from "lit/directives/map.js";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  private achievement = [35, 63, 96];

  // 渲染组件
  protected render() {
    return html` ${map(this.achievement, (value) => html`成绩是:${value}`)} `;
  }
}

在这里插入图片描述

map()是一个围绕for/of 循环的简单包装器,它使得在表达式中使用可迭代对象更容易一些。map()总是更新任何就地创建的 DOM - 它不做任何差异或 DOM 移动。如果涉及较多的dom操作使用repeat

大体意思就是当数组或对象改变后,map会重新创建所有的dom元素,这是比较浪费性能的

repeat
将可迭代的值渲染到 DOM 中,并使用可选的键控来启用数据差异和 DOM 稳定性。大体意思就是当渲染完成后,后续有变化时,只会更新有变化的数据。

//参数1是要迭代的数据,第二个参数是一个函数,返回一个唯一的key值,参数3是要返回的模板
repeat(items: Iterable<T>, keyfn: KeyFn<T>, template: ItemTemplate<T>)
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { repeat } from "lit/directives/repeat.js";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  private userList: Array<userI> = [
    {
      no: "001",
      name: "张三",
    },
    {
      no: "002",
      name: "李四",
    },
  ];

  // 渲染组件
  protected render() {
    return html`
      <ul>
        ${repeat(
          this.userList,
          (item: userI) => item.no,
          (item: userI) => html`<li>姓名:${item.name}</li>`
        )}
      </ul>
    `;
  }
}

interface userI {
  no: string;
  name: string;
}

在这里插入图片描述
join
joinjs 中的join用法一致
在这里插入图片描述

import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { join } from "lit/directives/join.js";
import { map } from "lit/directives/map.js";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  private urlList = [
    {
      name: "Lit",
      url: "https://lit.dev/",
    },
    {
      name: "vue",
      url: "https://cn.vuejs.org/",
    },
  ];

  // 渲染组件
  protected render() {
    return html`
      <div>
        ${join(
          map(
            this.urlList,
            (item) => html`<a href=${item.url}>${item.name}</a>`
          ),
          html`<span>|</span>`
        )}
      </div>
    `;
  }
}

在这里插入图片描述
range
start返回从 t​​o end(不包括)递增的整数的迭代step。

@customElement("base-app")
export class BaseApp extends LitElement {
  // 渲染组件
  protected render() {
    return html` <div>${range(1, 6)}</div> `;
  }
}

在这里插入图片描述

ifDefined
如果值已定义,则设置属性,如果未定义,则删除该属性。只能用在属性表达式中

当一个属性值中存在多个表达式时,如果任何表达式使用ifDefined并计算结果为undefined/ ,则该属性将被删除

 render() {
    return html`<img src="/images/${ifDefined(this.size)}/${ifDefined(this.filename)}">`;
  }

缓存和更改检测

cache
在更改模板而不是丢弃 DOM 时缓存渲染的 DOM。在大型模板之间频繁切换时,您可以使用此指令优化渲染性能。

当 Lit 重新渲染一个模板时,它只更新修改的部分:它不会创建或删除比需要更多的 DOM。但是当你从一个模板切换到另一个模板时,Lit 会移除旧的 DOM 并呈现一个新的 DOM 树。

const detailView = (data) => html`<div>...</div>`;
const summaryView = (data) => html`<div>...</div>`;

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  data = {showDetails: true, /*...*/ };

  render() {
    return html`${cache(this.data.showDetails
      ? detailView(this.data)
      : summaryView(this.data)
    )}`;
  }
}

keyed
将可渲染值与唯一键相关联。当 key 改变时,之前的 DOM 会在渲染下一个值之前被移除和处理,即使值(例如模板)是相同的。

keyed当您渲染有状态元素并且您需要确保在某些关键数据更改时清除元素的所有状态时非常有用。它本质上选择退出 Lit 的默认 DOM 重用策略。

keyed如果您需要为“进入”或“退出”动画强制使用新元素,在某些动画场景中也很有用。

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  userId: string = '';

  render() {
    return html`
      <div>
        ${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)}
      </div>`;
  }
}

guard
仅在其依赖项之一发生更改时重新评估模板,以通过防止不必要的工作来优化渲染性能。
类似vue中的监听,当某个值发生变化后,来执行某些操作。

guard(dependencies: unknown[], valueFn: () => unknown)
  • dependencies是用于监视更改的值数组。
  • valueFn是一个返回可渲染值的函数。
@customElement('my-element')
class MyElement extends LitElement {

  @property()
  value: string = '';

  render() {
    return html`
      <div>
        ${guard([this.value], () => calculateSHA(this.value))}
      </div>`;
  }
}

live

对于 DOM 值可能从 Lit 外部更改的情况很有用。例如,当使用表达式设置元素的value属性、内容可编辑元素的文本或更改其自身属性或属性的自定义元素时。

在这些情况下,如果 DOM 值发生了变化,但通过 Lit 表达式设置的值没有发生变化,Lit 将不知道要更新 DOM 值而将其置之不理。如果这不是你想要的——如果你想用绑定的值覆盖 DOM 值,无论如何——使用live()指令。

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  data = {value: 'test'};

  render() {
    return html`<input .value=${live(this.data.value)}>`;
  }
}

以对象来举例,当对象层级结构太深时,某一个属性改变了,但是lit监听不到这个值发生了变化,这是dom就不会更新,为了防止这种情况可以使用live来解决

引用渲染DOM

ref
检索对呈现到 DOM 中的元素的引用。

Ref对象充当对元素的引用的容器,并且可以使用模块中的辅助createRef方法创建ref。渲染后,Ref的value属性将设置为元素,可以在渲染后生命周期中访问它,如updated.

@customElement('my-element')
class MyElement extends LitElement {

  inputRef: Ref<HTMLInputElement> = createRef();

  render() {
    // Passing ref directive a Ref object that will hold the element in .value
    return html`<input ${ref(this.inputRef)}>`;
  }

  firstUpdated() {
    const input = this.inputRef.value!;
    input.focus();
  }
}

异步渲染

until
呈现占位符内容,直到一个或多个承诺解决。

接受一系列值,包括 Promises。值按优先级顺序呈现,第一个参数具有最高优先级,最后一个参数具有最低优先级。如果值是 Promise,则将呈现较低优先级的值,直到它解决。

值的优先级可用于为异步数据创建占位符内容。例如,带有待处理内容的 Promise 可以作为第一个(最高优先级)参数,而非承诺加载指示符模板可以用作第二个(低优先级)参数。加载指示器会立即呈现,并且主要内容将在 Promise 解析时呈现。

import {until} from 'lit/directives/until.js';

@customElement('my-element')
class MyElement extends LitElement {

  @state()
  private content = fetch('./content.txt').then(r => r.text());

  render() {
    return html`${until(this.content, html`<span>Loading...</span>`)}`;
  }
}

asyncAppend
asyncAppend呈现async iterable的值,将每个新值附加到前一个值之后。请注意,异步生成器还实现了异步可迭代协议,因此可以被asyncAppend.

import {asyncAppend} from 'lit/directives/async-append.js';

async function *tossCoins(count: number) {
  for (let i=0; i<count; i++) {
    yield Math.random() > 0.5 ? 'Heads' : 'Tails';
    await new Promise((r) => setTimeout(r, 1000));
  }
}

@customElement('my-element')
class MyElement extends LitElement {

  @state()
  private tosses = tossCoins(10);

  render() {
    return html`
      <ul>${asyncAppend(this.tosses, (v: string) => html`<li>${v}</li>`)}</ul>`;
  }
}

自定义指令

指令是可以通过自定义模板表达式的呈现方式来扩展 Lit 的函数。指令非常有用且功能强大,因为它们可以是有状态的、访问 DOM、在模板断开连接和重新连接时收到通知以及在渲染调用之外独立更新表达式。

除了内置指令外,还可以进行自定义指令。自定义指令分为两类:

  • 简单的功能
  • 基于类的指令

一个简单的函数返回一个要渲染的值。它可以接受任意数量的参数,或者根本没有参数。

export noVowels = (str) => str.replaceAll(/[aeiou]/ig,'x');

基于类的指令可以让你做一个简单的函数不能做的事情。使用基于类的指令:

  • 直接访问渲染的 DOM(例如,添加、删除或重新排序渲染的 DOM 节点)。
  • 在渲染之间保持状态。
  • 在渲染调用之外异步更新 DOM。
  • 当指令与 DOM 断开连接时清理资源

创建基于类的指令

要创建基于类的指令:

  • 将该指令实现为扩展该类的Directive类。
  • 将您的类传递给directive()工厂以创建可在 Lit 模板表达式中使用的指令函数。
//hello.ts

import { Directive, directive } from "lit/directive.js";

class HelloDirective extends Directive {
  render() {
    return `Hello!`;
  }
}
// Create the directive function
const hello = directive(HelloDirective);

export default hello;

//使用
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";

import hello from "./view/hello";

import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
  // 渲染组件
  protected render() {
    return html` <div>${hello()}</div> `;
  }
}

在这里插入图片描述

基于类的指令的生命周期

指令类有一些内置的生命周期方法:

  • 类构造函数,用于一次性初始化。
  • render(), 用于声明性渲染。
  • update(), 用于命令式 DOM 访问。

您必须render()为所有指令实现回调。使用update()是可选的。update()调用的默认实现并从render().

可以在正常更新周期之外更新 DOM 的异步指令使用一些额外的生命周期回调。

构造函数

class MyDirective extends Directive {

  value = 0;
  constructor(partInfo: PartInfo) {
    super(partInfo);
    console.log('MyDirective created');
  }
  ...
}

只要每次渲染在同一个表达式中使用相同的指令函数,前一个实例就会被重用,因此实例的状态在渲染之间保持不变。

构造函数接收单个PartInfo对象,该对象提供有关使用指令的表达式的元数据。这对于在指令设计为仅用于特定类型的表达式的情况下提供错误检查很有用

声明式渲染 render()

render()方法应该返回要渲染到 DOM 中的值。它可以返回任何可渲染的值,包括另一个DirectiveResult.

import { Directive, directive, PartInfo } from "lit/directive.js";

class HelloDirective extends Directive {
  constructor(partInfo: PartInfo) {
    super(partInfo);
    console.log("MyDirective created", partInfo);
  }

  render(msg: string) {
    return msg;
  }
}
// Create the directive function
const hello = directive(HelloDirective);

export default hello;
  protected render() {
    return html` <div>${hello('11')}</div> `;
  }

在这里插入图片描述

命令式DOM访问:update()

在更高级的用例中,您的指令可能需要访问底层 DOM 并强制读取或改变它。您可以通过覆盖update()回调来实现这一点。

update()回调接收两个参数:

  • Part具有用于直接管理与表达式关联的 DOM 的 API的对象。
  • render()包含参数的数组。

您的update()方法应该返回 Lit 可以渲染的东西,或者noChange如果不需要重新渲染,则返回特殊值。update()回调非常灵活,但典型用途包括:

  • 从 DOM 读取数据,并使用它来生成要渲染的值。
  • element使用对象上的orparentNode引用强制更新 DOM Part。在这种情况下,update()通常返回noChange,表示 Lit 不需要采取任何进一步的操作来呈现指令。

Part
每个表达式位置都有自己的特定Part对象:

  • ChildPart用于 HTML 子位置中的表达式。
  • AttributePart用于 HTML 属性值位置的表达式。
  • BooleanAttributePart用于布尔属性值中的表达式(名称以 为前缀?)。
  • EventPart用于事件侦听器位置中的表达式(名称以 为前缀@)。
  • PropertyPart用于属性值位置的表达式(名称以 为前缀.)。
  • ElementPart对于元素标签上的表达式。

除了包含的特定于部分的元数据之外PartInfo,所有Part类型都提供对element与表达式相关联的 DOM 的访问,可以直接在 中访问update()。例如:

import { Directive, directive, ChildPart } from "lit/directive.js";

class AttributeLogger extends Directive {
  //属性名称
  attributeNames = "";
  update(part: ChildPart) {
    this.attributeNames = (part.parentNode as Element)
      .getAttributeNames?.()
      .join(" ");
    return this.render();
  }
  render() {
    return `属性是:${this.attributeNames}`;
  }
}
const attributeLogger = directive(AttributeLogger);

export default attributeLogger;
protected render() {
  return html`
    <div>
      <div a b>${hello()}</div>
    </div>
  `;
}

在这里插入图片描述

表示没有变化 noChange

有时,指令可能没有任何新内容可供 Lit 渲染。您通过noChangeupdate()或者 render()方法返回来发出信号。这与返回不同undefined,后者会导致 Lit 清除Part与指令关联的内容。返回noChange将先前呈现的值留在原处。

import {Directive} from 'lit/directive.js';
import {noChange} from 'lit';
class CalculateDiff extends Directive {
  a?: string;
  b?: string;
  render(a: string, b: string) {
    if (this.a !== a || this.b !== b) {
      this.a = a;
      this.b = b;
      // Expensive & fancy text diffing algorithm
      return calculateDiff(a, b);
    }
    return noChange;
  }
}

将指令限制为一种表达式类型

某些指令仅在一种上下文中有用,例如属性表达式或子表达式。如果放置在错误的上下文中,该指令应该抛出一个适当的错误。

例如,该classMap指令验证它仅用于AttributePart并且仅用于class属性:

class ClassMap extends Directive {
  constructor(partInfo: PartInfo) {
    super(partInfo);
    if (
      partInfo.type !== PartType.ATTRIBUTE ||
      partInfo.name !== 'class'
    ) {
      throw new Error('The `classMap` directive must be used in the `class` attribute');
    }
  }
  ...
}

异步指令

前面的示例指令是同步的:它们从它们的render()/生命周期回调中同步返回值,因此它们的结果在组件的回调update()期间被写入 DOM 。update()

有时,您希望指令能够异步更新 DOM——例如,如果它依赖于网络请求等异步事件。

要异步更新指令的结果,指令需要扩展AsyncDirective提供setValue()API 的基类。允许指令在模板的正常/循环setValue()之外将新值“推送”到其模板表达式中。updaterender

class ResolvePromise extends AsyncDirective {
  render(promise: Promise<unknown>) {
    Promise.resolve(promise).then((resolvedValue) => {
      // Rendered asynchronously:
      this.setValue(resolvedValue);
    });
    // Rendered synchronously:
    return `Waiting for promise to resolve`;
  }
}
export const resolvePromise = directive(ResolvePromise);

在这里,渲染的模板显示“Waiting for promise to resolve”,然后是 promise 的已解析值,无论何时解析。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无知的小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值