JavaScript Library – Lit

前言

我写过一篇关于 Lit 的文章,Material Design, Angular Material, MDC, MWC, Lit 的关系

如今 material-web MWC 已经发布 1.0 了,估计 Angular 也会在不远的将来从 material-components-web MDC 迁移到 MWC。

以后,我们要想深入理解 Angular Material 就必须对 MWC 有一定了解,而 MWC 又是基于 Lit 开发的,所以我们也需要了解 Lit。

这篇就让我们来看看 Lit 吧。

 

参考

Lit 官网文档

 

Lit 介绍

Lit 的前生是 Google 的 Polymer。它是一个帮助我们写标准 Web Components 的库。

它有 2 个特点:

  1. 标准 W3C Web Components
    Lit 开发出来的组件是 W3C 规范的 Web Components,不像 Angular、React、Vue 那些都是仿冒的。 
    符合标准的好处是可以 Plug and Play,组件本身不依赖框架技术。
    我用 Lit 写出来的组件,可以拿到 Angular、React、Vue 任何项目里跑。
  2. 提升开发体验
    在不借助任何库的情况下,手写 W3C Web Components 开发体验是很差的,代码可读性也差。
    Lit 主要就是为了解决这些问题而诞生的。
    它借助 DecoratorTemplate literals 特性,实现了声明式定义组件和 MVVM。

 

Lit Getting Started

Lit 的目的是开发出 W3C Web Components,所以要掌握 Lit 就必须先掌握 W3C Web Components。

不熟悉的朋友们,请先看我以前写的这篇 DOM – Web Components

安装

Lit 是可以用在纯 HTML、CSS、JS 上的,但是会降低开发体验。所以我还是鼓励大家用 TypeScript。

我这里搭配 Vite 做演示,你想改用 Webpack 或 Rollup 也都可以。

yarn create vite

它默认会有一些 sample code,我们洗掉它,从一个干净的开始。

index.css

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

index.ts 清空

index.html

复制代码
<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Vite + Lit + TS</title>
  <link rel="stylesheet" href="./src/index.css" />
  <script type="module" src="/src/index.ts"></script>
</head>

<body>
</body>

</html>
复制代码

创建组件

Web Components 是由 HTML TemplatesShadow DOMCustom Elements 三种独立的技术搭配而成的。

而 Lit 把它们组合在一起了。

hello-world.ts

复制代码
import { LitElement, css, html } from 'lit';
import { customElement } from 'lit/decorators.js';

// 定义组件
// 取代了 window.customElements.define('hello-world', HelloWorldElement);
@customElement('hello-world')
export class HelloWorldElement extends LitElement {
  // 取代了 <template>
  render() {
    return html`<h1>Hello World</h1>`;
  }

  // 取代了 <style>
  static styles = css`
    h1 {
      color: red;
    }
  `;
}

// declare type for TypeScript
declare global {
  interface HTMLElementTagNameMap {
    'hello-world': HelloWorldElement;
  }
}
复制代码

HTML 和 CSS 用了 Template literals 技术。它不像 Angular 搞 compiler 黑魔法,这个只是单纯的 JS runtime render。

Tips: template literals 对 IDE 不友好,需要插件 lit-html 和 vscode-styled-components

使用组件

index.ts

import './hello-world';

setTimeout(() => {
  // 动态使用
  const helloWorld = document.createElement('hello-world');
  document.body.appendChild(helloWorld);
}, 3000);

记得要 import 先。

index.html

<body>
  <!-- 静态使用 -->
  <hello-world></hello-world>
</body>

效果

 

Lit の Shadow DOM

所有原生 Shadow DOM 的特性,在 Lit 都可以用。

比如::host<slot>::slotted()::part()

除了 :host-context(),相关提问:Stack Overflow – :host-context not working as expected in Lit-Element web component

主要是因为 Firefox 和 Safari 本来就不支持 :host-context 所以 Lit 干脆就完全不支持了。可以使用 CSS Variables 作为替代方案。

从这里也能看出,Lit 实现 Web Components 的手法我们直觉认为的不太一样,它里面动了一些手脚。

 

Lit の Custom Elements

所有原生 Custom Elements 的特性,在 Lit 都可以用。

比如 lifecycle:connectedCallback、disconnectedCallback、attributeChangedCallback、static get observedAttributes。

 

Lit の MVVM

到目前为止,我们看到的 @customElement、extends LitElement、html``、css`` 只是 Lit 的小角色。

真正让 Lit 发亮起来的是它的 MVVM,这也是 W3C Web Components 最缺失的功能。

MVVM 的中心思想

MVVM 的宗旨就是不要直接操作 DOM,不要调用 DOM API,凡事都通过 MVVM 库去控制。

Lit 提供了很多种 binding、listening、query 的方式去取代 DOM 操作。

binding & listening

复制代码
@customElement('hello-world')
export class HelloWorldElement extends LitElement {
  @state()
  private value = 'default value';

  private updateValue() {
    this.value = 'new value';
  }

  render() {
    return html`
      <h1>${this.value}</h1>
      <button @click="${this.updateValue}">update value</button>
    `;
  }

  static styles = css``;
}
复制代码

效果

如果你熟悉 Angular,那应该对 Lit 的语法不会感到太陌生。它俩其实挺像的。毕竟都是 Google 出品,师出同门嘛。

我们逐个来看

有一个 value 属性,@state() 表示这个属性会被用于模板。这时 Lit 就知道每当这个属性值发生变化,那就需要 re-render(它具体如何实现重渲染我不清楚,估计不会是大面积的替换,应该会做性能优化)

有一个 updateValue 方法,调用它就更新 value 属性。

把 value 属性插入模板。

@click 是一个特殊字符串,表示监听 click 事件,接着把 updateValue 方法插入模板。

至此,Lit 就掌握足够信息,可以做监听和 update DOM 了。

Attribute & Property

@state 和 @property 的区别是,一个是 private 一个是 public。

复制代码
@customElement('hello-world')
export class HelloWorldElement extends LitElement {
  @property({ type: Number, attribute: 'number-value' })
  numberValue!: number;

  @property({ type: Boolean, attribute: 'bool-value' })
  boolValue = false;

  render() {
    return html` <h1>${this.numberValue.toFixed(4)}</h1>
      <h1>${this.boolValue}</h1>`;
  }

  static styles = css``;
}
复制代码

外部 HTML 控制

<hello-world number-value="50" bool-value></hello-world>

外部 JS 操控

document.querySelector('hello-world')!.numberValue = 50;
document.querySelector('hello-world')!.boolValue = false;

Dispatch Event

复制代码
@customElement('hello-world')
export class HelloWorldElement extends LitElement {
  private handleClick() {
    this.dispatchEvent(new CustomEvent('clickhelloworld', { bubbles: true }));
  }

  render() {
    return html`<h1 @click="${this.handleClick}">Hello World</h1>`;
  }

  static styles = css``;
}
复制代码

外部监听

document.body.addEventListener('clickhelloworld', e => {
  console.log('clicked', e.target); // <hello-world>
});

注:组件 this.dispatchEvent 的这个 this 只的是 <hello-world> 这个 element。所以 event 不需要设置 composed

 

用 Lit 重写 Counter Component

在 DOM – Web Components 文章的结尾,我写了一个 Counter Component,我们现在用 Lit 重写一遍。

最终效果是这样

counter.ts

复制代码
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';

@customElement('counter-component')
export class HTMLCounterElement extends LitElement {
  @property({ type: Number })
  step = 1;

  @state()
  private number = 0;

  private minus() {
    this.number -= this.step;
  }

  private plus() {
    this.number += this.step;
  }

  render() {
    return html`
      <div class="counter">
        <button class="minus" @click="${this.minus}">-</button>
        <span class="number">${this.number}</span>
        <button class="plus" @click="${this.plus}">+</button>
      </div>
    `;
  }

  static styles = css`
    .counter {
      display: flex;
      gap: 16px;
    }
    .counter :is(.minus, .plus) {
      width: 64px;
      height: 64px;
    }
    .counter .number {
      width: 128px;
      height: 64px;
      border: 1px solid gray;
      font-size: 36px;
      display: grid;
      place-items: center;
    }
  `;
}

declare global {
  interface HTMLElementTagNameMap {
    'counter-component': HTMLCounterElement;
  }
}
复制代码

index.html

<counter-component step="10"></counter-component>

 

Lit 的局限

我们拿 material-web MWC 和 material-components-web MDC 做对比。

MDC 是传统手法,先有 HTML、CSS,然后 JS 做 binding。

MWC 是 Web Components 手法,没有 HTML、CSS,一切都是 JS 生成的。

结论就是 Lit 的渲染依赖于 JS。

如果我们用 Lit 开发企业网站,需要 SEO,那就需要搞服务端渲染 Server-side rendering。但这个目前还在 experimental 阶段。

 

posted @   兴杰  阅读(256)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
历史上的今天:
2021-12-13 EF Core – Many to Many
2021-12-13 Identity – 冷知识
2016-12-13 HttpClient
点击右上角即可分享
微信分享提示