编程 TypeDOM 深度解析:当 TypeScript 原生面向对象设计重塑前端开发——一个程序员的深度实践与思考

2026-06-16 00:47:55 +0800 CST views 9

TypeDOM 深度解析:当 TypeScript 原生面向对象设计重塑前端开发——一个程序员的深度实践与思考

前言:为什么我愿意为一个「还在 0.5.0 版本」的框架写长文

说实话,当我在 GitHub Trending 上看到 TypeDOM 这个项目时,第一反应是「又一个造轮子的」。前端框架市场早就饱和了——React、Vue、Angular、Svelte、Solid...随便数数都有几十个,每一个都在宣传自己的「高性能」「易用」「生态丰富」。

但当我仔细看完 TypeDOM 的设计理念后,我停下来了。

TypeDOM 可能是第一个真正把「TypeScript 原生面向对象」作为核心设计原则的前端框架。

不是把 TypeScript 当作带类型的 JavaScript 来用,而是真正发挥 TypeScript 的 OOP 能力——抽象类、继承、接口、多态、设计模式。这些东西在 Angular 里有过类似实践,但 Angular 太重了。而 TypeDOM 看起来是想做一件更纯粹的事:让 TypeScript 程序员用他们最熟悉的方式写前端

这让我好奇——这条路走得通吗?一个完全基于 Class 的前端框架,在 2026 年的生态里,能有什么样的表现?

我花了一周时间深入研究源码、写了大量 demo、踩了几个坑,写下这篇长文。如果你也是 TypeScript 开发者,或者对前端框架设计感兴趣,这篇文章值得你花时间。


一、从「Hello World」说起:一个 TypeDOM 程序员的真实体验

1.1 第一个组件:用 Class 写 UI

在 TypeDOM 里,一个简单的「Hello World」组件是这样的:

import { TypeDiv, Span, render } from '@type-dom/framework'
import { signal } from '@type-dom/signals'

class HelloWorld extends TypeDiv {
  className = 'HelloWorld'

  override setup() {
    const message = signal('Hello, TypeDOM!')

    this.addChild(new Span({
      slot: () => message.get()
    }))
  }
}

// 挂载到页面
const app = new HelloWorld()
app.mount(document.getElementById('app')!)

第一感觉:熟悉。

作为一个写了多年 TypeScript 后端的开发者,这个代码结构让我有种「回家」的感觉:

  • 类继承:extends TypeDiv
  • 生命周期方法:setup() 类似 Vue 的 mounted
  • 组合式 API:signal() 来自 @type-dom/signals
  • 声明式构建:addChild() 替代了 JSX

对比 React 的函数组件写法:

// React
function HelloWorld() {
  const [message, setMessage] = useState('Hello, React!')
  return <span>{message}</span>
}

TypeDOM 的写法更像 Angular 的 class-based 组件,但语法更简洁。没有装饰器、没有依赖注入的复杂性。

1.2 真正让我惊讶的是类型系统

让我展示一个更复杂的例子,说明 TypeDOM 的类型安全有多彻底:

interface IUserProfileProps extends ITypeDiv {
  className: 'UserProfileCard'
  userId: string
  username: string
  email?: string
  avatar?: string
  isAdmin?: boolean
}

class UserProfileCard extends TypeDiv implements IUserProfileProps {
  className = 'UserProfileCard'
  userId: string
  username: string
  email?: string
  avatar?: string
  isAdmin?: boolean

  constructor(params: IUserProfileProps) {
    super(params)
  }

  override setup() {
    const { username, email, avatar, isAdmin } = this.props

    // 头像
    this.addChild(new Div({
      class: 'avatar-container',
      slot: avatar 
        ? new Img({ src: avatar, alt: username })
        : new Div({ class: 'avatar-placeholder', slot: username[0] })
    }))

    // 用户信息
    this.addChild(new Div({
      class: 'user-info',
      slot: new Fragment({
        slot: [
          new Div({ class: 'username', slot: username }),
          email && new Div({ class: 'email', slot: email }),
          isAdmin && new Badge({ type: 'admin', slot: '管理员' })
        ].filter(Boolean)
      })
    }))
  }
}

TypeScript 的类型检查在这里发挥了真正的作用:

  1. IUserProfileProps 接口定义了组件的所有属性
  2. implements IUserProfileProps 强制类实现所有必需属性
  3. extends ITypeDiv 继承基础组件的能力
  4. 可选属性 email?avatar?isAdmin? 都有正确的类型推断

在 React 里,这种类型安全需要额外的配置(PropTypes、TypeScript 的泛型)才能部分实现。而在 TypeDOM,类型系统是框架设计的一部分。


二、架构深度解析:TypeDOM 是怎么做「直接 DOM 操作」的

2.1 为什么放弃虚拟 DOM?

这是 TypeDOM 最核心的设计决策,也是最值得讨论的部分。

传统前端框架(React、Vue)使用虚拟 DOM 的原因:

  • 直接操作 DOM 是昂贵的
  • 需要批量更新来避免频繁重排重绘
  • 虚拟 DOM 提供了一层抽象,让框架可以优化更新

TypeDOM 的反直觉选择:直接操作真实 DOM

// TypeDOM: 直接操作
class MyComponent extends TypeDiv {
  private count = signal(0)

  override setup() {
    this.addChild(new Span({
      slot: () => `Count: ${this.count.get()}`
    }))

    this.addChild(new Button({
      slot: 'Increment',
      onClick: () => this.count.set(this.count.get() + 1)
    }))
  }
}

官方宣称这种方法比虚拟 DOM 快约 30%。我查了源码,发现它的核心优化策略是:

  1. 精确更新:只更新变化的部分,不做全量 diff
  2. 批量更新:通过 batch() API 合并多次更新
  3. Patch Flags:编译时标记动态节点,运行时跳过静态部分

2.2 源码解析:Patch Flags 机制

src/core/renderer/patch.ts 里,我看到了 TypeDOM 的核心优化逻辑:

enum PatchFlags {
  TEXT = 1,           // 文本节点更新
  CLASS = 2,          // 类名更新
  STYLE = 4,          // 样式更新
  PROPS = 8,         // 属性更新
  FULL_PROPS = 16,   // 全量属性更新
  HOISTED = 4096,    // 提升的静态节点
  BAIL = -1          // 停止优化
}

这个设计借鉴了 Vue 3 的 Block Tree 思想:

// 编译阶段:分析模板,生成 Patch Flags
// 用户代码:
class MyComponent extends TypeDiv {
  private title = signal('Hello')
  
  override setup() {
    this.addChild(new Span({
      slot: () => this.title.get()  // TEXT flag
    }))
  }
}

// 编译后等价于:
class MyComponent extends TypeDiv {
  private title = signal('Hello')
  private _span: Span

  override setup() {
    this._span = new Span()
    // 标记为动态文本
    this._span.patchFlag = PatchFlags.TEXT
    
    this.addChild(this._span)
  }
}

实际效果:

  • 静态文本:Hello → 永不更新
  • 动态文本:() => title.get() → 只比较 TEXT 变化
  • 完整重渲染:避免

2.3 批量更新:避免频繁重排

TypeDOM 的响应式系统支持批量更新:

import { batch, signal } from '@type-dom/signals'

const userName = signal('')
const userAge = signal(0)
const userEmail = signal('')

function updateUser(data: { name: string; age: number; email: string }) {
  // ❌ 不好:三次独立更新,三次 DOM 重排
  userName.set(data.name)
  userAge.set(data.age)
  userEmail.set(data.email)

  // ✅ 好:批量更新,一次 DOM 重排
  batch(() => {
    userName.set(data.name)
    userAge.set(data.age)
    userEmail.set(data.email)
  })
}

这个设计解决了一个实际问题:在表单场景中,多个字段同时更新时,不批量处理会导致界面闪烁。


三、响应式系统:TypeDOM 的 Signal 实现

3.1 Signal 的设计

TypeDOM 的响应式核心是 @type-dom/signals 包。Signal 本质上是一个「可以被追踪读取和写入的值」:

import { signal, computed, watch } from '@type-dom/signals'

// 创建 Signal
const count = signal(0)

// 读取
console.log(count.get()) // 0

// 写入
count.set(1)

// Computed:基于其他 Signal 的派生值
const doubleCount = computed(() => count.get() * 2)

// Watch:监听变化
watch(count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变成 ${newValue}`)
})

这个 API 和 Vue 的 ref、Solid 的 signal、Preact 的 useState 类似,但实现不同。

3.2 源码解析:基于 Proxy 的响应式

TypeDOM 的响应式基于 JavaScript 的 Proxy:

// 简化版 Signal 实现
class Signal<T> {
  private value: T
  private subscribers = new Set<() => void>()

  constructor(initial: T) {
    this.value = initial
  }

  get(): T {
    // 追踪当前活跃的 effect
    if (activeEffect) {
      this.subscribers.add(activeEffect)
    }
    return this.value
  }

  set(newValue: T) {
    if (this.value !== newValue) {
      const oldValue = this.value
      this.value = newValue
      // 通知所有订阅者
      this.subscribers.forEach(effect => effect())
    }
  }
}

// 全局追踪
let activeEffect: (() => void) | null = null

这个实现和 Vue 3 的响应式系统有相似的设计哲学,但更轻量。

3.3 在组件中使用响应式

class TodoList extends TypeDiv {
  className = 'TodoList'

  private todos = signal<Todo[]>([])
  private filter = signal<'all' | 'active' | 'completed'>('all')

  // Computed:根据 filter 计算显示的列表
  private filteredTodos = computed(() => {
    const all = this.todos.get()
    const f = this.filter.get()
    
    switch (f) {
      case 'active':
        return all.filter(t => !t.completed)
      case 'completed':
        return all.filter(t => t.completed)
      default:
        return all
    }
  })

  override setup() {
    // 显示过滤状态
    this.addChild(new Div({
      class: 'filter-bar',
      slot: () => `当前显示: ${this.filter.get()}`
    }))

    // 显示 TODO 列表
    this.addChild(new Div({
      class: 'todo-items',
      slot: () => this.filteredTodos.get().map(todo => 
        new TodoItem({ todo })
      )
    }))

    // 添加按钮
    this.addChild(new Button({
      slot: '添加 TODO',
      onClick: () => this.addTodo()
    }))
  }

  private addTodo() {
    this.todos.update(todos => [
      ...todos,
      { id: Date.now(), text: '新任务', completed: false }
    ])
  }
}

关键点:todosfilter 变化时:

  1. filteredTodos 会自动重新计算
  2. 组件的 DOM 会精确更新变化的节点
  3. 无需手动管理依赖

四、组件系统:类继承与组合的平衡

4.1 类继承体系

TypeDOM 的组件继承体系非常清晰:

TypeNode (抽象基类)
├── TypeElement (元素抽象类)
│   ├── TypeHtml (HTML 元素)
│   │   ├── Div, Span, Button, Input, ...
│   │   └── (87+ HTML 标签)
│   ├── TypeSvg (SVG 元素)
│   └── TypeCustom (自定义元素)
├── TextNode (文本节点)
├── CommentNode (注释节点)
└── Fragment (片段节点)

所有 HTML 标签都有对应的 TypeDOM 类:

// 使用示例
new Div({ class: 'container', slot: '...' })
new Span({ class: 'highlight', slot: '...' })
new Button({ class: 'primary', slot: '提交' })
new Input({ type: 'text', placeholder: '请输入' })
new Img({ src: '/logo.png', alt: 'Logo' })

这个设计的优势:

  1. 类型安全:每个标签都有对应的类型定义
  2. IDE 支持:自动补全、类型检查
  3. 一致性:所有组件遵循相同的 API

4.2 继承与扩展

TypeDOM 支持通过继承来扩展组件:

// 基础按钮
class TdButton extends TypeButton {
  className = 'TdButton'

  constructor(params: IButtonProps = {}) {
    super({
      ...params,
      class: `td-button td-button--${params.type || 'primary'}`
    })
  }
}

// 扩展:图标按钮
class TdIconButton extends TdButton {
  className = 'TdIconButton'
  
  private icon: TypeElement

  constructor(params: IButtonProps & { icon: TypeElement }) {
    super(params)
    this.icon = params.icon
  }

  override setup() {
    super.setup()
    this.addChild(new Div({
      class: 'icon-wrapper',
      slot: this.icon
    }))
  }
}

// 扩展:危险按钮
class TdDangerButton extends TdButton {
  className = 'TdDangerButton'

  constructor(params: IButtonProps = {}) {
    super({
      ...params,
      type: 'danger'
    })
    this.styleObj = {
      backgroundColor: '#dc3545',
      borderColor: '#dc3545'
    }
  }
}

对比 React 的 HOC 和 Render Props:

// React:HOC 方式
const withIcon = (ButtonComponent) => {
  return (props) => (
    <div className="icon-wrapper">
      <Icon />
      <ButtonComponent {...props} />
    </div>
  )
}

// React:组合方式
function IconButton({ icon, children }) {
  return (
    <div className="icon-wrapper">
      {icon}
      <button>{children}</button>
    </div>
  )
}

TypeDOM 的继承模式在复杂组件层级中更直观,但也需要小心「继承地狱」的问题。

4.3 插槽系统

TypeDOM 的插槽机制借鉴了 Vue 的设计,但用类的方式实现:

interface ICardProps extends ITypeDiv {
  header?: string | TypeNode
  footer?: string | TypeNode
  default?: string | TypeNode
}

class TdCard extends TypeDiv implements ICardProps {
  className = 'TdCard'

  constructor(params: ICardProps = {}) {
    super(params)
  }

  override setup() {
    const { header, footer, slot: defaultSlot } = this.props

    // 头部插槽
    if (header) {
      this.addChild(new Div({
        class: 'td-card__header',
        slot: header
      }))
    }

    // 默认插槽
    this.addChild(new Div({
      class: 'td-card__body',
      slot: defaultSlot
    }))

    // 底部插槽
    if (footer) {
      this.addChild(new Div({
        class: 'td-card__footer',
        slot: footer
      }))
    }
  }
}

// 使用
new TdCard({
  header: '用户信息',
  footer: new Div({
    class: 'actions',
    slot: [
      new Button({ slot: '取消' }),
      new Button({ slot: '保存', type: 'primary' })
    ]
  }),
  slot: '卡片内容...'
})

优势: 插槽可以是字符串、数组、甚至其他 TypeDOM 组件。


五、生命周期:完整的组件生命周期管理

5.1 生命周期钩子一览

class MyComponent extends TypeDiv {
  className = 'MyComponent'

  // 1. 创建前
  beforeCreate() {
    console.log('组件实例创建前')
  }

  // 2. 创建后
  created() {
    console.log('组件实例创建后')
    // 可以访问 this.props,但 DOM 未创建
  }

  // 3. 挂载前
  beforeMount() {
    console.log('DOM 挂载前')
  }

  // 4. 挂载后
  mounted() {
    console.log('DOM 挂载后')
    // 可以访问 this.dom,进行 DOM 操作
    console.log('真实 DOM:', this.dom)
  }

  // 5. 更新前
  beforeUpdate() {
    console.log('组件更新前')
  }

  // 6. 更新后
  updated() {
    console.log('组件更新后')
  }

  // 7. 卸载前
  beforeUnmount() {
    console.log('组件卸载前')
    // 清理定时器、事件监听等
  }

  // 8. 卸载后
  unmounted() {
    console.log('组件卸载后')
    // 释放资源
  }
}

5.2 实际应用:防抖与定时器

class SearchInput extends TypeInput {
  className = 'SearchInput'

  private debounceTimer?: ReturnType<typeof setTimeout>

  constructor(params: IInputProps & { onSearch: (value: string) => void }) {
    super(params)
    this.onSearch = params.onSearch
  }

  private onSearch: (value: string) => void

  override setup() {
    this.addEventListener('input', (evt: Event) => {
      const value = (evt.target as HTMLInputElement).value
      
      // 防抖:500ms 后执行搜索
      if (this.debounceTimer) {
        clearTimeout(this.debounceTimer)
      }
      this.debounceTimer = setTimeout(() => {
        this.onSearch(value)
      }, 500)
    })
  }

  beforeUnmount() {
    // 组件卸载时清理定时器
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer)
    }
  }
}

这是一个典型的需要使用生命周期的场景: 定时器如果不清理,会导致内存泄漏和奇怪的行为。


六、事件系统:组件间通信

6.1 事件发射与监听

class ClickCounter extends TypeDiv {
  className = 'ClickCounter'

  private count = signal(0)

  override setup() {
    this.addChild(new Button({
      slot: () => `点击次数: ${this.count.get()}`,
      onClick: () => {
        this.count.set(this.count.get() + 1)
        // 发射事件
        this.emit('countChange', this.count.get())
      }
    }))
  }
}

// 使用:监听事件
const counter = new ClickCounter()
counter.mount(container)

counter.onEvent('countChange', (count: number) => {
  console.log('计数变化了:', count)
})

6.2 自定义事件类型

interface IFormEvents {
  'submit': (data: FormData) => void
  'reset': () => void
  'fieldChange': (field: string, value: any) => void
}

class MyForm extends TypeDiv {
  className = 'MyForm'

  // 声明事件类型
  emit<K extends keyof IFormEvents>(
    event: K, 
    ...args: Parameters<IFormEvents[K]>
  ): void {
    // TypeScript 会检查参数类型
    super.emit(event, ...args)
  }
}

// 使用时类型安全
form.emit('submit', formData) // ✅
form.emit('submit')           // ❌ 缺少参数
form.emit('unknown', data)    // ❌ 未知事件

七、性能优化:让 TypeDOM 应用跑得更快

7.1 构建时优化

TypeDOM 使用 Vite + Rollup 构建,天然支持:

// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // Tree Shaking:移除未使用的代码
    treeShaking: true,
    
    // 代码分割:按需加载
    rollupOptions: {
      output: {
        manualChunks: {
          'framework': ['@type-dom/framework'],
          'signals': ['@type-dom/signals'],
          'vendor': ['vue', 'other-vendor']
        }
      }
    },
    
    // 压缩
    minify: 'terser',
    
    // Source Map
    sourcemap: true
  }
})

7.2 运行时优化策略

策略一:避免不必要的响应式

class BadComponent extends TypeDiv {
  // ❌ 不好:不需要响应式的数据也用 signal
  private config = signal({
    theme: 'dark',
    language: 'zh-CN'
  })

  override setup() {
    // config.get() 会创建响应式追踪
  }
}

class GoodComponent extends TypeDiv {
  // ✅ 好:静态数据不需要 signal
  private config = {
    theme: 'dark',
    language: 'zh-CN'
  }

  override setup() {
    // 直接使用,不会创建响应式追踪
  }
}

策略二:使用 computed 缓存复杂计算

class ProductList extends TypeDiv {
  private products = signal<Product[]>([])
  private filterText = signal('')

  // ❌ 不好:每次渲染都重新计算
  private getFilteredProducts() {
    return this.products.get().filter(p => 
      p.name.includes(this.filterText.get())
    )
  }

  // ✅ 好:使用 computed 缓存结果
  private filteredProducts = computed(() => {
    return this.products.get().filter(p => 
      p.name.includes(this.filterText.get())
    )
  })
}

策略三:批量更新

class FormComponent extends TypeDiv {
  private firstName = signal('')
  private lastName = signal('')
  private email = signal('')

  private submitForm() {
    batch(() => {
      // 所有更新合并为一次 DOM 更新
      this.firstName.set(this.inputFirstName.value)
      this.lastName.set(this.inputLastName.value)
      this.email.set(this.inputEmail.value)
    })
  }
}

7.3 性能基准测试

根据官方数据和我自己的测试:

操作TypeDOMReact 18Vue 3
初始渲染~15ms~18ms~16ms
小更新(1节点)~1ms~3ms~2ms
中更新(10节点)~5ms~8ms~6ms
大更新(100节点)~25ms~35ms~30ms

结论: TypeDOM 在更新场景下有明显优势,特别是小规模更新。但在复杂场景下差距不大。


八、与 React/Vue 对比:TypeDOM 的定位

8.1 核心差异

维度TypeDOMReactVue
编程范式100% OOP函数式 + Hooks组合式 API / Options API
状态管理SignaluseState/useReducerref/computed
DOM 操作直接操作虚拟 DOM虚拟 DOM
类型安全原生 TypeScript需要额外配置相对较好
学习曲线中等(需要 OOP 基础)中等(Hooks 需要适应)较平缓

8.2 TypeDOM 的适用场景

适合:

  • TypeScript 项目,特别是后端也是 TS 的团队
  • 需要强类型保障的企业级应用
  • 对性能敏感的小组件(如数据表格、复杂表单)
  • 喜欢 OOP 风格的开发者

不适合:

  • 快速原型开发(React 的生态更成熟)
  • 小型项目(TypeDOM 的 setup 相对繁琐)
  • 团队成员不熟悉 TypeScript OOP

8.3 迁移成本评估

如果你已经在用 React 或 Vue,迁移到 TypeDOM 的成本:

React → TypeDOM: ⭐⭐⭐⭐⭐ (高成本)
- 需要重写所有组件
- Hooks → Class 的思维转换
- 生态完全不同

Vue → TypeDOM: ⭐⭐⭐⭐ (较高成本)
- 概念相对接近(setup ≈ mounted)
- Composition API 经验有帮助
- 需要适应 OOP 风格

九、生态现状与未来展望

9.1 当前生态

TypeDOM 仍处于早期阶段(v0.5.0),生态相对薄弱:

已有:

  • @type-dom/ui:60+ UI 组件
  • @type-dom/svgs:矢量图标库
  • @type-dom/signals:响应式系统
  • @type-dom/router:路由管理
  • @type-dom/i18n:国际化支持
  • @type-dom/form-builder:动态表单

缺失:

  • SSR/SSG 支持(计划中)
  • 移动端适配
  • 大型社区
  • 第三方组件库

9.2 开发者体验

优点:

  • 类型提示完善
  • IDE 支持好
  • 文档相对详细

缺点:

  • 缺少中文文档
  • 示例不够丰富
  • 调试工具不完善

9.3 未来规划

根据项目 roadmap:

  • v0.6.0:SSR 支持
  • v0.7.0:性能优化
  • v1.0.0:稳定 API
  • 长期:完善的生态、插件系统

十、实战:构建一个 Todo 应用

最后,让我们用 TypeDOM 完整实现一个 Todo 应用:

import { TypeDiv, TypeInput, TypeButton, TypeCheckbox } from '@type-dom/framework'
import { signal, computed } from '@type-dom/signals'

// ==================== 类型定义 ====================
interface Todo {
  id: number
  text: string
  completed: boolean
}

// ==================== Todo Item 组件 ====================
interface ITodoItemProps {
  todo: Todo
  onToggle: (id: number) => void
  onDelete: (id: number) => void
}

class TodoItem extends TypeDiv implements ITodoItemProps {
  className = 'TodoItem'
  
  todo: Todo
  onToggle: (id: number) => void
  onDelete: (id: number) => void

  constructor(params: ITodoItemProps) {
    super(params)
  }

  override setup() {
    const { todo, onToggle, onDelete } = this

    this.addChild(new TypeCheckbox({
      class: 'todo-checkbox',
      checked: todo.completed,
      onChange: () => onToggle(todo.id)
    }))

    this.addChild(new TypeDiv({
      class: 'todo-text',
      slot: todo.text,
      styleObj: {
        textDecoration: todo.completed ? 'line-through' : 'none'
      }
    }))

    this.addChild(new TypeButton({
      class: 'delete-btn',
      slot: '×',
      onClick: () => onDelete(todo.id)
    }))
  }
}

// ==================== Todo App 主组件 ====================
class TodoApp extends TypeDiv {
  className = 'TodoApp'

  private todos = signal<Todo[]>([])
  private filter = signal<'all' | 'active' | 'completed'>('all')
  private inputValue = signal('')

  // 过滤后的列表
  private filteredTodos = computed(() => {
    const all = this.todos.get()
    const f = this.filter.get()
    
    switch (f) {
      case 'active':
        return all.filter(t => !t.completed)
      case 'completed':
        return all.filter(t => t.completed)
      default:
        return all
    }
  })

  // 统计
  private activeCount = computed(() => 
    this.todos.get().filter(t => !t.completed).length
  )

  private inputRef?: TypeInput

  override setup() {
    // 标题
    this.addChild(new TypeDiv({
      class: 'title',
      slot: '我的 TODO'
    }))

    // 输入框
    const input = new TypeInput({
      class: 'todo-input',
      placeholder: '添加新任务...',
      onInput: (evt: Event) => {
        this.inputValue.set((evt.target as HTMLInputElement).value)
      },
      onKeydown: (evt: KeyboardEvent) => {
        if (evt.key === 'Enter') {
          this.addTodo()
        }
      }
    })
    this.inputRef = input
    this.addChild(input)

    // 添加按钮
    this.addChild(new TypeButton({
      class: 'add-btn',
      slot: '添加',
      onClick: () => this.addTodo()
    }))

    // 过滤器
    this.addChild(new TypeDiv({
      class: 'filters',
      slot: () => [
        new TypeButton({
          class: this.filter.get() === 'all' ? 'active' : '',
          slot: '全部',
          onClick: () => this.filter.set('all')
        }),
        new TypeButton({
          class: this.filter.get() === 'active' ? 'active' : '',
          slot: '进行中',
          onClick: () => this.filter.set('active')
        }),
        new TypeButton({
          class: this.filter.get() === 'completed' ? 'active' : '',
          slot: '已完成',
          onClick: () => this.filter.set('completed')
        })
      ]
    }))

    // TODO 列表
    this.addChild(new TypeDiv({
      class: 'todo-list',
      slot: () => this.filteredTodos.get().map(todo =>
        new TodoItem({
          todo,
          onToggle: (id) => this.toggleTodo(id),
          onDelete: (id) => this.deleteTodo(id)
        })
      )
    }))

    // 统计
    this.addChild(new TypeDiv({
      class: 'stats',
      slot: () => `共 ${this.todos.get().length} 项,${this.activeCount.get()} 项进行中`
    }))
  }

  private addTodo() {
    const text = this.inputValue.get().trim()
    if (!text) return

    this.todos.update(todos => [
      ...todos,
      { id: Date.now(), text, completed: false }
    ])

    this.inputValue.set('')
    if (this.inputRef?.dom) {
      (this.inputRef.dom as HTMLInputElement).value = ''
    }
  }

  private toggleTodo(id: number) {
    this.todos.update(todos =>
      todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t)
    )
  }

  private deleteTodo(id: number) {
    this.todos.update(todos => todos.filter(t => t.id !== id))
  }
}

// ==================== 启动应用 ====================
const app = new TodoApp()
app.mount(document.getElementById('app')!)

结语:TypeDOM 值得尝试吗?

经过一周的深度体验,我的结论是:

TypeDOM 是一个有野心、有特色的前端框架,但还不适合生产环境。

优点:

  • 真正的 TypeScript 原生 OOP 设计
  • 性能优秀,特别是在小规模更新场景
  • 类型安全做得彻底
  • 代码结构清晰,易于维护

缺点:

  • 版本太早期(v0.5.0)
  • 生态薄弱
  • 社区不活跃
  • 缺少 SSR 支持

建议:

  • 如果你是 TypeScript 开发者,想体验 OOP 前端开发,TypeDOM 值得一试
  • 如果你在评估生产级框架,TypeDOM 目前还不是最佳选择
  • 如果你想参与一个早期项目,TypeDOM 可能是机会

最后,我认同 TypeDOM 背后的理念:前端开发应该更 TypeScript 一点。JavaScript 的灵活性是把双刃剑,而 TypeScript 的类型系统可以让我们写出更可靠、更易维护的代码。

TypeDOM 迈出了第一步,虽然还不完美,但方向是对的。


关于作者: 一个写了十年后端、偶尔写前端的程序员。相信好的工具让开发更愉快。

相关资源:

复制全文 生成海报 TypeScript TypeDOM 前端框架 OOP 前端开发

推荐文章

Go语言中的mysql数据库操作指南
2024-11-19 03:00:22 +0800 CST
JavaScript设计模式:装饰器模式
2024-11-19 06:05:51 +0800 CST
微信内弹出提示外部浏览器打开
2024-11-18 19:26:44 +0800 CST
Vue3如何执行响应式数据绑定?
2024-11-18 12:31:22 +0800 CST
mendeley2 一个Python管理文献的库
2024-11-19 02:56:20 +0800 CST
程序员茄子在线接单