Lit 的响应式系统为什么这么轻?——从 ReactiveElement 的设计说起

在使用 Lit 时,很多人都会有一个直观感受:

"它居然没有状态管理系统?"
"也没有复杂的依赖收集?"

但实际使用下来却发现:

  • 状态更新是正确的
  • DOM 更新是精准的
  • 性能非常稳定

那么问题来了:

Lit 的响应式系统到底是怎么工作的?
它为什么可以这么轻?

本文将从 ReactiveElement 的设计目标、实现方式、与 Vue/React 的根本差异 三个角度来解释这个问题。


一、先说结论(Important)

在 Lit 中:

响应式系统的职责不是"计算依赖",
而是"调度更新"

这是理解 Lit 响应式系统的第一把钥匙。


二、ReactiveElement 在 Lit 架构中的位置

先回顾 Lit 的继承链:

text 复制代码
HTMLElement
  └── ReactiveElement
        └── LitElement
              └── YourComponent

其中:

  • ReactiveElement:负责响应式属性 & 更新调度
  • LitElement:负责生命周期 & render 调用
  • lit-html:负责 DOM 更新

ReactiveElement 不关心 DOM

它只关心:什么时候需要更新


三、Lit 的响应式系统到底"响应"了什么?

3.1 Lit 的响应式单位是「属性」,不是「依赖」

在 Vue 中:

ts 复制代码
effect(() => {
  div.textContent = state.count
})
  • 响应的是 依赖关系
  • 自动追踪 state.count

而在 Lit 中:

ts 复制代码
@property()
count = 0
  • 响应的是 属性赋值行为
  • 不存在依赖收集

3.2 本质区别

框架 响应式关注点
Vue "谁用到了这个值"
React "状态是否发生变化"
Lit "属性是否被 set"

Lit 的选择非常克制。


四、ReactiveElement 的核心机制

4.1 @property 做了什么

ts 复制代码
@property({ type: Number })
count = 0

背后本质是:

  1. 把字段变成 getter / setter
  2. 在 setter 中调用 requestUpdate

4.2 伪代码示意

ts 复制代码
set count(value) {
  const oldValue = this._count
  this._count = value
  this.requestUpdate('count', oldValue)
}

没有 Proxy

没有依赖收集

没有 effect


五、requestUpdate:Lit 响应式系统的核心

5.1 requestUpdate 并不立即更新

ts 复制代码
this.requestUpdate()

它做的事情是:

  • 标记"需要更新"
  • 把更新放入 microtask 队列
  • 合并多次更新

5.2 批量更新机制

ts 复制代码
this.count = 1
this.count = 2
this.count = 3

最终只会:

text 复制代码
render 一次

这是 Lit 响应式系统最重要的性能保证。


5.3 更新调度流程

text 复制代码
property set
  ↓
requestUpdate
  ↓
Promise.resolve().then()
  ↓
performUpdate
  ↓
render()

六、为什么 Lit 不需要依赖收集?

答案非常关键:

因为 lit-html 已经知道"哪里会变"

在前一篇 Part 更新机制中讲过:

  • DOM 更新是由 Part 精确定位的
  • render() 每次都会重新执行
  • 但更新只发生在表达式对应位置

因此:

Lit 不需要知道"你在模板中用了哪些属性"

它只需要保证:

  • 属性变化 → render 执行
  • render 执行 → Part 精确更新

七、shouldUpdate:Lit 给你的"唯一判断点"

ts 复制代码
shouldUpdate(changedProperties) {
  return true
}
  • changedProperties 是一个 Map
  • 记录了哪些属性发生了变化

你可以非常明确地控制:

ts 复制代码
shouldUpdate(changed) {
  return changed.has('count')
}

这比 Vue/React 的依赖优化更显式、更可控。


八、Lit 的生命周期为什么这么少?

Lit 的生命周期几乎完全围绕「更新」展开:

ts 复制代码
connectedCallback()
shouldUpdate()
willUpdate()
render()
updated()

没有:

  • computed
  • watch
  • effect
  • memo

因为:

Lit 不试图管理你的状态
它只负责把状态变化"反映到 DOM"


九、和 Vue / React 的响应式系统对比

9.1 架构层面对比

维度 Lit Vue React
响应式基础 getter/setter Proxy setState
依赖收集 ❌ ✅ ❌
更新调度 microtask scheduler scheduler
DOM 更新 定点 Part Diff Diff
系统复杂度 极低 高 中

9.2 为什么 Lit 可以这么"简单"

因为 Lit 放弃了三件事:

  1. 自动依赖追踪
  2. 状态派生(computed)
  3. 跨组件状态管理

这些都不是组件底层必须解决的问题。


十、Lit 响应式系统的适用边界

10.1 非常适合的场景

  • UI 组件
  • Design System
  • 跨框架组件
  • 微前端子模块

10.2 不适合的场景

  • 大型应用状态管理
  • 复杂状态派生逻辑
  • 需要大量 computed / watch 的业务

Lit 的哲学是:

把"状态复杂度"交给使用者或上层架构


十一、A Very Important 总结

Lit 的响应式系统不是"弱",
而是"目标非常明确"

它只做三件事:

  1. 监听属性变化
  2. 合并更新
  3. 触发 render

而剩下的一切,都交给:

  • JavaScript 本身
  • 浏览器本身
  • 应用架构本身

十二、最后

到现在应该可以已经完整理解了:

  1. Lit 的整体架构
  2. 为什么不需要 Virtual DOM
  3. lit-html 的 Part 更新机制
  4. ReactiveElement 的响应式设计

如果把这些拼起来,you 会发现:

Lit 是一个"极端相信浏览器"的体系