Custom Elements 生命周期与 Lit 的映射关系详解

很多人在使用 Lit 时,会有一种“不太踏实”的感觉:

生命周期好像不多?
没有 beforeMount / mounted / beforeUpdate?
这真的够用吗?

原因在于:

Lit 并没有“发明生命周期”,
它只是忠实地站在 Custom Elements 之上。

本文将从 浏览器原生生命周期 出发,解释:

  • Custom Elements 提供了什么
  • Lit 在这些生命周期上做了哪些封装
  • Lit 的生命周期为什么这么“少”
  • 与 Vue / React 生命周期的本质差异

一、先给结论(Very Important)

Lit 的生命周期不是“简化版 Vue”,
而是“Custom Elements 生命周期 + 更新阶段钩子”。

理解这一点,后面一切都会顺。


二、Custom Elements 的原生生命周期

Web Components(Custom Elements)规范只定义了 4 个生命周期回调

class MyElement extends HTMLElement {
  constructor() {}
  connectedCallback() {}
  disconnectedCallback() {}
  attributeChangedCallback() {}
}

就这么多,没有多余概念。


2.1 constructor

  • 元素被创建
  • 尚未插入 DOM
  • 不应该访问 DOM / attributes
constructor() {
  super()
}

2.2 connectedCallback

  • 元素被插入文档
  • 可以访问 DOM
  • 可能被多次调用(移动节点)
connectedCallback() {
  console.log('attached')
}

2.3 disconnectedCallback

  • 元素从文档移除
  • 用于清理副作用
disconnectedCallback() {
  console.log('detached')
}

2.4 attributeChangedCallback

  • 监听 attribute 变化
  • 必须显式声明 observedAttributes

三、Lit 是如何“站在”这些生命周期之上的

Lit 并没有绕过这些生命周期,而是:

在 Custom Elements 生命周期中插入“更新流程”


3.1 LitElement 的基本结构

class LitElement extends ReactiveElement {
  render() {}
}

真正的魔法发生在 ReactiveElement 中。


四、Lit 的完整生命周期执行顺序(重点)

当一个 Lit 组件被使用时,实际执行顺序是:

constructor
  ↓
connectedCallback
  ↓
第一次 requestUpdate
  ↓
shouldUpdate
  ↓
willUpdate
  ↓
render
  ↓
updated

这是真正需要理解的生命周期链


五、Lit 生命周期逐个拆解

5.1 constructor

constructor() {
  super()
}

特点:

  • 不能访问 DOM
  • 不要在这里做副作用
  • 不要 set 响应式属性(会触发更新)

5.2 connectedCallback

connectedCallback() {
  super.connectedCallback()
}

用途:

  • 启动副作用(事件监听、observer)
  • 定时器
  • 与外部系统建立连接

注意:

connectedCallback ≠ mounted

它可能被调用多次。


5.3 requestUpdate(隐式生命周期)

Lit 中没有显式的 beforeMount
requestUpdate 实际上就是更新的起点

this.requestUpdate()

5.4 shouldUpdate

shouldUpdate(changedProps) {
  return true
}

这是 Lit 给你的唯一更新拦截点

  • 返回 false → 跳过整个更新流程
  • 非常适合做性能控制

5.5 willUpdate

willUpdate(changedProps) {}

用途:

  • 基于属性变化,派生内部状态
  • 类似 Vue 的 watch + computed

不要在这里操作 DOM


5.6 render(核心)

render() {
  return html`...`
}

特点:

  • 每次更新都会执行
  • 必须是纯函数
  • 不应该产生副作用

5.7 updated

updated(changedProps) {}

用途:

  • DOM 已经更新完成
  • 安全操作 DOM
  • 类似 Vue 的 updated / mounted

5.8 disconnectedCallback

disconnectedCallback() {
  super.disconnectedCallback()
}

用途:

  • 清理副作用
  • 取消事件 / observer / timer

六、Lit 生命周期为什么“这么少”?

这是一个设计结果,不是能力不足。

6.1 Lit 把生命周期拆成两类

  1. 元素生命周期(来自浏览器)
  2. 更新生命周期(围绕 render)

而不是像 Vue 那样混合在一起。


6.2 对比 Vue 生命周期

Vue Lit
beforeCreate constructor
mounted firstUpdated / updated
beforeUpdate shouldUpdate
updated updated
unmounted disconnectedCallback

Lit 的生命周期不是少,而是更贴近底层模型


七、一个非常容易踩的坑(Important)

错误认知

“connectedCallback 只会执行一次”

正确认知

只要元素被移除再插入,就会再次执行

因此:

  • 不要在这里假设“只初始化一次”
  • 真正的一次性逻辑可以放在 firstUpdated

八、firstUpdated:Lit 的“妥协点”

firstUpdated() {
  // 只执行一次
}

这是 Lit 提供的唯一一个非原生生命周期,目的只有一个:

满足开发者对“mounted once”的直觉需求


九、为什么 Lit 不提供更多生命周期?

因为:

  1. 生命周期越多,心智负担越重
  2. 浏览器模型已经足够清晰
  3. 多余的钩子会掩盖真实执行顺序

Lit 的选择是:

让开发者面对真实模型,而不是被抽象包裹


十、在工程实践中的建议(IIImportant)

10.1 推荐的使用方式

场景 生命周期
初始化状态 constructor
启动副作用 connectedCallback
派生状态 willUpdate
DOM 操作 updated / firstUpdated
清理资源 disconnectedCallback

十一、一个核心结论(Remember)

Lit 的生命周期不是“不够用”,
而是“不替你做决定”。

它把控制权交还给:

  • JavaScript
  • 浏览器
  • 架构设计者
posted @ 2025-12-23 10:34  幼儿园技术家  阅读(6)  评论(0)    收藏  举报