跳转至

Topic | 代码风格与 React Compiler 集成

重要性:⭐⭐⭐(读懂 Claude Code 源码的"语法"前提) 出现位置:几乎所有 .tsx 顶部 关联glossary 的 React Compiler 词条


1. React Compiler 痕迹

Claude Code 大量使用 React Compiler(React 19+ 的优化编译器)。每个 .tsx 文件顶部都有:

import { c as _c } from "react/compiler-runtime"

这是 React Compiler 编译后的产物: - 源文件用 React 19 风格写(不需要 useMemo / useCallback) - 编译器自动分析依赖,插入 memoization - 编译后生成 _c(N) 引用,N 是 memo cache 槽位

示例(编译前 vs 编译后):

// 编译前(你写的)
function Dialog({ title, onCancel, children }) {
  return (
    <Box flexDirection="column">
      <Text>{title}</Text>
      {children}
    </Box>
  )
}

// 编译后(React Compiler 生成)
import { c as _c } from "react/compiler-runtime"
function Dialog(t0) {
  const $ = _c(27);  // 27 = memo cache 槽位数
  // ... 编译器分析 props 依赖,生成缓存检查代码
  return ...
}

关键: - t0 是 props 形参(编译器重命名了) - _c(27) 是 memoization 入口 - 源代码里看起来怪,是因为这是编译产物

2. import / 命名习惯

2.1 显式 .js 后缀(即使源是 .ts)

import { foo } from './bar.js'   // ✅ Claude Code 风格
import { foo } from './bar'      // ❌ 不写后缀

原因: - Claude Code 用 Bun 运行时 + ESM - ESM 规范要求显式扩展名 - 配合 TypeScript 的 moduleResolution: "bundler" 工作 - 即使源是 bar.ts,import 写 bar.js(TS 解析时映射到 .ts)

好处: - 跨运行时一致(Node ESM、Bun、Deno) - TypeScript 配置 allowImportingTsExtensions 不会出错

2.2 biome / biome-ignore 注释

// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
import { feature } from 'bun:bundle'
import { readFileSync } from 'fs'
// ...

含义: - 项目用 biome(不是 ESLint + Prettier) - biome 默认会自动重排 import - 但 Claude Code 的 ANT-ONLY 标记有顺序要求require 必须在 import 后) - // biome-ignore 禁用这一行规则

实战: - 看到 // biome-ignore 就知道这块代码有特殊考虑 - 不要"按 biome 提示"自动修复

2.3 ESLint 自定义规则

// eslint-disable-next-line custom-rules/no-top-level-side-effects
process.env[k] ??= '1'

Claude Code 用自定义 ESLint 规则: - custom-rules/no-top-level-side-effects —— 禁止顶层副作用 - custom-rules/no-process-env-top-level —— 禁止顶层读 env - custom-rules/safe-env-boolean-check —— 安全布尔检查

意义: - 项目有严格的代码规范 - 不是个人喜好,是深思熟虑的工程决策 - 例:no-top-level-side-effects 是因为 import 顺序会影响启动行为

3. JSDoc 注释风格

/**
 * True when a task is in a terminal state and will not transition further.
 * Used to guard against injecting messages into dead teammates, evicting
 * finished tasks from AppState, and orphan-cleanup paths.
 */
export function isTerminalTaskStatus(status: TaskStatus): boolean {
  return status === 'completed' || status === 'failed' || status === 'killed'
}

特点: - 注释解释为什么(不是什么) - 列出使用场景 - 三段式:summary + use cases + implementation

4. TypeScript 风格

4.1 type vs interface

// Claude Code 几乎全部用 type,不用 interface
export type Tool = { name: string, description: string, ... }
export type Message = UserMessage | AssistantMessage | ...
export type AppState = { ... }

为什么: - type 支持 union(A | B) - type 支持 intersection(A & B) - type 支持 mapped type - interface 不能做 union(只能 extends)

4.2 Discriminated Union

export type Message =
  | { type: 'user', text: string }
  | { type: 'assistant', text: string, toolUses: ToolUse[] }
  | { type: 'tool_use', tool: string, input: unknown }
  | { type: 'tool_result', result: ToolResult }

配套类型守卫

function isUserMessage(m: Message): m is Extract<Message, { type: 'user' }> {
  return m.type === 'user'
}

4.3 DeepImmutable

export type DeepImmutable<T> = {
  readonly [K in keyof T]: DeepImmutable<T[K]>
}

export type ToolPermissionContext = DeepImmutable<{
  allowedTools: Set<string>
  deniedTools: Set<string>
  // ...
}>

意义: - 整个对象递归 readonly - TypeScript 防止意外 mutation - 运行时不实际freeze(性能考虑)

5. 函数式风格 vs OOP

5.1 大多数是函数

export function findToolByName(tools: Tools, name: string): Tool | undefined {
  return tools.find(tool => toolMatchesName(tool, name))
}

5.2 少数用 class

export class QueryEngine {
  private retryCount = 0
  private totalCost = 0

  async *ask(messages: Message[]): AsyncGenerator<AskEvent> { ... }
}

什么时候用 class: - 跨调用的状态(retryCount 在多次 ask 之间累加) - DI 容器(QueryEngine 接受一堆依赖) - 极少数场景

什么时候用函数: - 纯计算 - 一次性操作 - 工具函数

哲学默认函数,需要状态用 class

6. 错误处理

6.1 三种风格

// 风格 1:try/catch + 转换
try {
  return await someAsyncOp()
} catch (err) {
  throw toError(err)  // 转成标准 Error
}

// 风格 2:Result 类型
type Result<T> = { ok: true, value: T } | { ok: false, error: Error }

// 风格 3:自定义 Error 子类
export class FallbackTriggeredError extends Error {
  constructor(public originalError: Error) {
    super(`Fallback triggered: ${originalError.message}`)
  }
}

6.2 错误命名

  • *Error 后缀(不是 *Exception
  • 描述性名字(RateLimitErrorAuthenticationError

7. 关键洞察

7.1 React Compiler 是"未来的 React"

Claude Code 是早期采用 React Compiler 的项目。读源码看到的"怪样子"是编译产物,不是手写代码。

7.2 严格 TypeScript 是项目"安全网"

  • type-only imports
  • DeepImmutable
  • discriminated union + type guards
  • 运行时类型 + 编译时类型双保险

7.3 自定义 ESLint 规则体现"团队规范"

  • no-top-level-side-effects 反映"启动顺序敏感"
  • no-process-env-top-level 反映"env 读取要可控"
  • 规则的本质是团队共识的代码化

8. 阅读清单

  1. ✅ 任意 .tsx 文件顶部 —— 看 React Compiler 痕迹
  2. src/state/store.ts —— 函数式 + 严格类型
  3. src/QueryEngine.ts —— 少数 class 用法
  4. src/Task.ts —— discriminated union
  5. 📌 .eslintrc / biome.json —— 实际规则定义
  6. 📌 tsconfig.json —— 严格模式

9. 练习任务

  1. 用 React Compiler 编译你的代码 —— 比较前后差异
  2. 写一个 DeepImmutable —— 用 mapped type 递归
  3. 实现 isUserMessage 类型守卫 —— 让 TS 智能收窄类型
  4. 思考:如果你是 Claude Code 团队负责人,你会加哪些自定义 ESLint 规则?为什么?