用 Lit 构建跨 Vue / React 的组件库完整实践

在前面的几篇中,已经把 Lit 的原理层拉出来溜了一圈:

  • 它为什么不需要 Virtual DOM
  • lit-html 如何通过 Part 精确更新 DOM
  • 响应式系统为什么极度克制
  • 生命周期为什么贴近浏览器模型
  • 为什么几乎没有状态管理

现在不再停留在"理解"上,而是发出灵魂的拷问,问出真正有工程价值的问题

为什么 Lit 特别适合用来构建「跨框架组件库」?
以及,应该怎么做?


一、为什么"跨框架组件库"是一个真实需求

在真实业务中,经常会遇到以下情况:

  • 公司内部同时存在 Vue / React / 原生项目
  • Design System 希望 统一 UI 规范
  • 组件需要被第三方系统接入
  • 微前端架构下,不同子应用技术栈不同

如果用 Vue / React 写组件库,通常意味着:

组件和框架强绑定

这正是 Lit 的优势所在。


二、为什么 Lit 天然适合这件事

2.1 Lit 的组件是「标准 Web Components」

Lit 组件本质是:

html 复制代码
<my-button></my-button>

而不是:

tsx 复制代码
<MyButton />

这意味着:

  • Vue / React / Angular / 原生都能用
  • 没有运行时依赖冲突
  • 不需要桥接层

2.2 对比三种方案

方案 可行性 代价
Vue 写 Vue 组件库 仅 Vue 框架绑定
React 写 React 组件库 仅 React 框架绑定
Lit 写 Web Components 全部 学习成本

Lit 的代价是前期理解,
收益是长期复用能力。


三、组件库整体架构设计

3.1 推荐的目录结构

text 复制代码
packages/
├── components/
│   ├── button/
│   │   ├── button.ts
│   │   ├── button.styles.ts
│   │   └── index.ts
│   ├── modal/
│   └── index.ts
├── theme/
├── tokens/
└── utils/

特点:

  • 组件高度独立
  • 不依赖任何框架上下文
  • 只依赖浏览器标准

3.2 组件基类设计

ts 复制代码
export class BaseElement extends LitElement {
  static shadowRootOptions = {
    mode: 'open',
    delegatesFocus: true,
  }
}

所有组件统一继承,方便:

  • 注入主题
  • 统一行为约束
  • 控制 Shadow DOM 行为

四、一个 Button 组件的完整实现思路

4.1 定义组件

ts 复制代码
@customElement('ui-button')
export class UIButton extends BaseElement {
  @property({ type: String }) type = 'default'
  @property({ type: Boolean }) disabled = false

  render() {
    return html`
      <button ?disabled=${this.disabled}>
        <slot></slot>
      </button>
    `
  }
}

特点:

  • API 极其稳定
  • 属性即接口
  • 没有框架侵入

4.2 样式隔离策略

ts 复制代码
static styles = css`
  button {
    padding: 8px 16px;
  }
`
  • Shadow DOM 原生隔离
  • 不污染宿主项目
  • 不怕样式冲突

五、在 Vue / React 中如何使用

5.1 在 Vue 3 中使用

html 复制代码
<ui-button type="primary">
  提交
</ui-button>

注意点:

  • Vue 对 Web Components 是一等公民
  • 只需在 compilerOptions.isCustomElement 中声明

5.2 在 React 中使用

tsx 复制代码
<ui-button type="primary">
  Submit
</ui-button>

注意:

  • 事件使用 addEventListener
  • 或通过属性回调
ts 复制代码
button.addEventListener('click', handler)

六、事件设计:跨框架的关键点

6.1 不要暴露框架事件

不推荐:

ts 复制代码
this.$emit('change')

推荐:

ts 复制代码
this.dispatchEvent(
  new CustomEvent('change', {
    detail: value,
    bubbles: true,
    composed: true
  })
)

这是 Web Components 标准事件模型


七、主题与样式系统设计

7.1 使用 CSS Variables

css 复制代码
:host {
  --btn-bg: #1677ff;
}

好处:

  • 框架无关
  • 可运行时切换
  • 可被外部覆盖

7.2 Design Token 层

ts 复制代码
export const colors = {
  primary: 'var(--color-primary)',
}

形成:

text 复制代码
Token → CSS Variables → Component Styles

八、构建与发布策略

8.1 构建目标

  • 输出 原生 ES Module
  • 不打包 Lit 本身(peer dependency)
  • 支持 tree-shaking
ts 复制代码
build: {
  target: 'es2020',
  lib: {
    entry: 'index.ts',
    formats: ['es']
  }
}

8.2 使用方式

ts 复制代码
import '@your-scope/ui-components'

即可全局注册组件。


九、Lit 在微前端中的优势

在微前端场景下,Lit 的优势会被放大:

  • 不共享运行时
  • 不依赖框架上下文
  • 不怕版本冲突
  • 天然沙箱友好

十、什么时候不该用 Lit?

必须说清楚边界:

不适合:

  • 大型业务应用
  • 复杂状态流转
  • 强路由依赖

适合:

  • 组件库
  • Design System
  • 基础 UI
  • 跨团队 / 跨系统复用

十一、A Very Important 结论

Lit 并不是"更好的 React / Vue",
而是"更接近浏览器的组件模型"。

当你的目标是:

  • 长期维护
  • 跨技术栈
  • 低运行时成本

Lit 往往是最优解。


最后

如果 you 理解了 Lit,就会发现:

前端并不一定要"越来越重",
也可以选择"回到浏览器本身"。