Lit 教程
基于 React 心智的 Lit 入门教程
介绍
本文介绍皆基于 Lit 3.1.0 版本。根据 React 的心智来描述 Lit。在使用 Lit 之前建议先了解 Web Component,Lit 是基于 Web Component 实现的框架。
Lit 定义组件的方式与 Web Component 相同,使用 customElements.define 来注册自定义元素,代码示例中的 MyElement 通常是传入元素的类。Lit 使用自定义元素,可以构建任意 Web 页面,且无需考虑框架环境——React 和 Vue 都可以嵌入 Lit 组件,因此更适合带有一定状态管理需求的通用组件开发。
if (!customElements.get("my-element")) {
customElements.define("my-element", MyElement);
}渲染
基础渲染方式
Lit 的渲染只需要使用 html 包裹模板,通过组件 render() 方法返回,即可渲染 DOM。变量通过 ${} 进行插值,与大多数框架类似。
列表遍历可以直接使用 map,与 React 类似,也可以通过函数返回模板。
组件化开发也非常方便,只需导入另一个组件即可。
import { LitElement, html } from "lit";
import "./my-header.js";
const name = "hello Lit";
const list = [1, 2, 3, 4];
class MyElement extends LitElement {
render() {
return html`
<p>Hello from my template.${name}</p>
${list.map((item) => {
return html`<div>${item}</div>`;
})} ${this.divDom()}
<my-header></my-header>
`;
}
divDom() {
return html`<div>divDom</div>`;
}
}
customElements.define("my-element", MyElement);import { LitElement, html } from "lit";
class MyHeader extends LitElement {
render() {
return html` <header>header</header> `;
}
}
customElements.define("my-header", MyHeader);Slot 插槽
普通插槽只需使用 <slot></slot> 占位:
<my-element>
<p>Render me in a slot</p>
</my-element>export class MyElement extends LitElement {
protected render() {
return html`
<p><slot></slot></p>
`;
}
}命名插槽
命名 slot 需要双方共同命名:
<div slot="box">box</div>
<slot name="box"></slot>具备 slot 属性的元素将只会渲染在对应命名的插槽中。
状态
声明状态
在静态 properties 中声明属性(类似 React state,但本质不同)。
class MyElement extends LitElement {
static properties = {
mode: { type: String },
data: { attribute: false },
};
constructor() {
super();
this.data = {};
}
}状态更新流程
- 调用组件 setter 方法
- setter 调用
requestUpdate - 比较(
newValue !== oldValue) - 检测到变化,则安排异步更新(多次变更合并)
- 调用
update()更新模板
复杂类型更新(Array、Object)
复杂类型不会触发更新(引用相等),因此:
- 使用不可变数据更新:赋新对象
- 或手动调用
this.requestUpdate()
观察属性(attribute)与反应属性(reflect)
attribute
Lit 会将所有公共属性映射为 HTML 特性(小写)。
若 attribute: false 则不会创建观察属性。
示例:
<my-element myvalue="99"></my-element>reflect
reflect: true 会将属性变化同步回 DOM 特性,可用于 CSS 或 DOM API。
export class App extends LitElement {
static get properties() {
return {
title: { type: String, reflect: true },
};
}
render() {
return html`
<div>${this.title}</div>
<button @click=${() => (this.name = "click name")}>click</button>
`;
}
}通信
父向子(值传递)
与 React 类似,通过标签直接传递属性——但不能传递函数。
<my-lit props="propsValue"></my-lit>子组件:
static properties = { props: { type: String } };父子双向通信(事件)
通过事件监听实现,自定义事件用 @ 监听。
下面示例展示 Shadow DOM 冒泡事件 + addEventListener:
HTML
<my-component></my-component>
<script type="module" src="../src/index.js"></script>父组件
import { LitElement, html } from "lit";
import "./component/input";
class MyComponent extends LitElement {
createRenderRoot() {
const root = super.createRenderRoot();
root.addEventListener("mylogin", (e) => {
console.log(e.detail, "addEventListener");
});
return root;
}
change(e) {
console.log(e.detail, "自定义事件");
}
render() {
return html`
<div @mylogin=${this.change}>这是我的Lit组件 <my-input></my-input></div>
`;
}
}
customElements.define("my-component", MyComponent);子组件
import { LitElement, html } from "lit";
class MyInput extends LitElement {
static properties = {
value: { type: String },
props: { type: Object },
};
constructor() {
super();
this.value = "";
}
render() {
return html` <input
@input=${(e) => {
this.dispatchEvent(
new CustomEvent("mylogin", {
bubbles: true,
composed: true,
cancelable: true,
detail: e.target.value,
})
);
}}
>
input
</input>
`;
}
}
customElements.define("my-input", MyInput);响应事件(表达式)
Lit 基于原生事件机制。
.value=${xxx} 等价于:inputEl.value = xxx
<input .value="${this.itemCount}" />事件绑定:
<div @click="${this.onClick}">click</div>示例:
import { render, html } from "lit-html";
class MyComponent {
constructor() {
this.value = "";
}
render() {
return html`
<input
value=${this.value}
@input=${(e) => {
this.value = e.target.value;
}}
/>
`;
}
}生命周期
Lit 组件是标准自定义元素,拥有 Web Component 生命周期:
class MyCustomElement extends HTMLElement {
static observedAttributes = ["color", "size"];
constructor() {
super();
}
connectedCallback() {
console.log("added");
}
disconnectedCallback() {
console.log("removed");
}
adoptedCallback() {
console.log("moved");
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(`Attribute ${name} changed.`);
}
}
customElements.define("my-custom-element", MyCustomElement);Lit 还实现了自己的 反应式更新生命周期:
- 属性初始化:
constructor - 强制更新:
this.requestUpdate() - 适合发起请求:
firstUpdated
Shadow DOM 与样式
Lit 使用 Shadow DOM,具备样式与结构隔离能力。
获取 DOM:
this.renderRoot.querySelector("#box");内联样式:
import style from './index.css';
render() {
return html` <style>
/* updated per instance */
</style>
<div>template content</div>`;
}总结
Lit 适合构建跨框架、通用、可插拔的基础组件,优点是与原生一致、不依赖框架 runtime,可在 React、Vue 中使用。
缺点也明显:
- 生态与 React/Vue 相比更弱
- 使用 Shadow DOM 导致样式无法外部覆盖,组件开发成本上升
- 大型项目使用有学习成本
若在 React 中使用 Lit,请阅读相关文档。