TypeScript/React/Node.js 简明教程

目标:用最短时间获得看懂源码的能力。不追求全面,只讲源码中实际出现的语法和模式。


第一天:JavaScript 核心(3 小时)

1.1 变量声明

// 你已有的知识:
// Java:    final String x = "hi";     String y = "hi";
// C++:     const auto x = "hi";      auto y = "hi";

// TypeScript:
const x = "hi";   // 不可变绑定(首选)—— 类似 Java final
let y = "hi";     // 可变绑定 —— 类似普通变量
var z = "hi";     // ❌ 永远不用,有作用域陷阱

1.2 函数

// 三种写法,源码中都会出现:

// 1. 普通函数(类似 Java/C++)
function add(a: number, b: number): number {
    return a + b;
}

// 2. 箭头函数(lambda)—— 最常用
const add = (a: number, b: number): number => a + b;
// Java: (a, b) -> a + b
// C++: [](int a, int b) { return a + b; }

// 3. 方法简写(在对象字面量中)
const obj = {
    value: 1,
    add(x: number) { return this.value + x; }
};

1.3 解构 —— 源码中无处不在

// 对象解构 —— 从对象中提取字段
const { name, aliases } = tool;          // 等价于: const name = tool.name; const aliases = tool.aliases;
const { data: result } = response;       // 重命名: const result = response.data;

// 数组解构
const [first, second] = items;           // 等价于: const first = items[0]; const second = items[1];
const [a, ...rest] = items;             // rest 是剩余元素数组

// 函数参数解构 —— 源码中极常见
function render({ theme, verbose, tools }: Options) { ... }
render({ theme: "dark", verbose: true, tools: [] });

1.4 展开运算符 ...

// 对象合并 —— 类似浅拷贝
const merged = { ...defaults, ...userOverrides };
// 后面的覆盖前面的。等价于: 先复制 defaults 所有字段,再用 userOverrides 覆盖

// 数组展开
const all = [...builtIn, ...mcp];       // [builtIn中的所有元素, mcp中的所有元素]

1.5 可选链和空值合并

// ?. —— 安全访问,遇 null/undefined 短路
const name = tool?.mcpInfo?.serverName;  // 任何一环为 null 时返回 undefined
// Java 等价: Optional.ofNullable(tool).map(t -> t.mcpInfo).map(m -> m.serverName)

// ?? —— 为 null/undefined 提供默认值
const val = input ?? "default";
// 注意:与 || 的区别 —— ?? 只有 null/undefined 才用默认值,|| 在 0、""、false 时也用默认值

1.6 模板字符串

const msg = `找到 ${count} 个文件,耗时 ${duration}ms`;
// Java:  String.format("找到 %d 个文件,耗时 %dms", count, duration)
// C++:   std::format("找到 {} 个文件,耗时 {}ms", count, duration)

1.7 数组操作 —— 源码高频出现

const tools = getAllBaseTools();               // 获取所有工具

// filter —— 过滤
const enabled = tools.filter(t => t.isEnabled());   // Java: stream().filter(t -> t.isEnabled())

// map —— 转换
const names = tools.map(t => t.name);                // Java: stream().map(t -> t.getName())

Map = 跟 Java HashMap 一样的键值对,只是语法从 put/get 变成了 set/get。

// flatMap —— 展开后映射
const allTools = groups.flatMap(g => g.tools);       // Java: stream().flatMap(g -> g.tools.stream())

flatMap = 映射后自动拍平一层嵌套,


// find —— 查找第一个匹配
const tool = tools.find(t => t.name === 'Bash');     // Java: stream().filter(t -> ...).findFirst()

// some / every
const hasMcp = tools.some(t => t.isMcp);             // 任一匹配 → true
const allSafe = tools.every(t => t.isConcurrencySafe()); // 全部匹配 → true

// uniqBy —— 去重(lodash)
const deduped = uniqBy(tools, 'name');               // 按 name 去重

// 链式调用
const names = tools
    .filter(t => t.isEnabled())
    .map(t => t.name)
    .sort();

第二天:异步编程(3 小时)—— 最重要的概念转换

2.1 你的已有知识对比

Java 多线程:                      JavaScript 异步:

Thread t = new Thread(() -> {    const result = await doWork();
    doWork();                    console.log(result);
});                              // ❌ 不是多线程!
t.start();                       // 没有阻塞,事件循环继续
t.join();                        // 其他代码可以在"等待"期间运行

// 有真正的并行                  // 单线程 + 事件循环(用户态)
// 内存共享,需要加锁            // 不需要锁
// 线程切换有开销                // await/async 几乎零开销

核心理解:JS 的 await 不会阻塞整个程序,只是暂停当前函数,把控制权还给事件循环。

2.2 Promise —— 异步值的容器

// Promise 有三种状态:pending → fulfilled(成功) | rejected(失败)

// 创建 Promise
const promise = fetch("https://api.example.com/data");
// 类型: Promise<Response>  —— "将来会有一个 Response 的承诺"

// 消费 Promise —— 方式一:.then()
fetch(url)
    .then(response => response.json())   // 成功后继续
    .then(data => console.log(data))     // 再继续
    .catch(err => console.error(err));   // 任何一步失败

// 消费 Promise —— 方式二:await(推荐,源码中主要使用)
const response = await fetch(url);
const data = await response.json();
console.log(data);

// 并行等待 —— 类似 Java 的 join() 多个线程
const [user, posts] = await Promise.all([
    fetchUser(),      // 同时开始
    fetchPosts(),     // 同时开始
]);

2.3 async 函数

// async 关键字标记函数:该函数内部可以使用 await
async function getData(): Promise<string> {
    const response = await fetch(url);  // 暂停,等结果
    return response.text();             // 自动包装为 Promise<string>
}

// 等价于箭头函数
const getData = async (): Promise<string> => {
    const response = await fetch(url);
    return response.text();
};

// 一句话:async 让函数返回 Promise,await 取出 Promise 里的值

2.4 错误处理

// try-catch 可以直接捕获 await 的 rejection
try {
    const data = await fetchData();
    console.log(data);
} catch (err) {
    console.error("获取失败:", err);
}

// 等价于 Java:
// try {
//     Data data = future.get();  // 阻塞等待
// } catch (ExecutionException e) { ... }

2.5 源码中的异步模式识别

// 模式 1:工具 call 方法都是 async
async call(input: BashInput, context: ToolUseContext): Promise<ToolResult<string>> {
    const output = await execCommand(input.command);  // 等待命令执行
    return { data: output };
}

// 模式 2:权限检查也是 async
async checkPermissions(input: BashInput, context: ToolUseContext): Promise<PermissionResult> {
    const isAllowed = await checkAllowlist(input.command);  // 等待检查
    if (isAllowed) return { behavior: 'allow', updatedInput: input };
    return { behavior: 'ask', updatedInput: input };
}

// 模式 3:流式 API 用 for-await-of
for await (const event of stream) {   // 逐块接收
    if (event.type === 'content_block_delta') {
        text += event.delta.text;
    }
}

第三天:TypeScript 类型系统(3 小时)

3.1 基础类型注解

// 类型在变量名后面(不是 Java/C++ 的前置风格)
const name: string = "Example";          // Java: String name = "Example"
const count: number = 42;               // Java: int count = 42 (TS 不区分 int/long/float/double)
const enabled: boolean = true;          // Java: boolean enabled = true
const items: string[] = ["a", "b"];    // Java: String[] items = {"a", "b"}
const map: Map<string, number> = new Map();  // 泛型同 Java
const maybe: string | null = null;      // Java: @Nullable String maybe (联合类型)

// 可选属性
interface Config {
    name: string;
    debug?: boolean;     // ? 表示可选 —— Java: @Nullable Boolean debug
}

3.2 Interface 和 Type

// interface —— 定义对象形状(跟 Java interface 不同,这里是数据结构)
interface ToolResult<T> {
    data: T;
    newMessages?: Message[];
}

// type —— 类型别名,更灵活
type Status = "pending" | "running" | "done";     // 联合类型(Java 需要 enum)
type StringOrNumber = string | number;             // 联合类型
type Result<T> = { ok: true; value: T } | { ok: false; error: string };  // 判别联合
type Callback = (x: number) => string;             // 函数类型

// interface vs type:源码中两者都用,大部分场景可以互换

3.3 泛型 —— 源码核心模式

// Example Code 的工具泛型层次:
// Tool<Input, Output, Progress>   ← 三个类型参数

// 简化版理解:
interface Tool<Input, Output> {       // 类似 Java: interface Tool<I, O>
    inputSchema: ZodType<Input>;       // 运行时 schema
    call(input: Input): Promise<Output>; // 类型安全的调用
}

// 使用:
const readTool: Tool<ReadInput, string> = {
    inputSchema: readSchema,        // TypeScript 推导 ReadInput 类型
    async call(input) {
        // input 有完整的 ReadInput 类型提示
        return input.filePath;      // ✅ 类型安全
    }
};

// extends 约束 —— 限制泛型参数
interface Tool<Input extends AnyObject> { ... }
// 类似 Java: <I extends Map<String, Object>>

3.4 关键工具类型(源码中常见)

// Partial<T> —— 所有字段变可选
interface User { name: string; age: number; }
type PartialUser = Partial<User>;  // { name?: string; age?: number; }

// Pick<T, K> —— 选取部分字段
type UserName = Pick<User, 'name'>;  // { name: string }

// Omit<T, K> —— 排除部分字段
type WithoutAge = Omit<User, 'age'>;  // { name: string }

// ReturnType<F> —— 获取函数返回值类型
type R = ReturnType<typeof myFunc>;

// Awaited<T> —— 解开 Promise
type Value = Awaited<Promise<string>>;  // string

3.5 as 类型断言

// as —— 告诉编译器"相信我,我知道这是什么类型"
const tool = findTool(tools, name) as BashTool;  // 窄化类型

// as const —— 字面量类型
const modes = ["read", "write", "execute"] as const;
// 类型: readonly ["read", "write", "execute"](不是 string[])
type Mode = typeof modes[number];  // "read" | "write" | "execute"

3.6 类型守卫 —— 运行时收窄类型

// typeof 守卫
if (typeof x === "string") { /* x 在此作用域是 string */ }

// 自定义守卫
function isAssistantMessage(msg: Message): msg is AssistantMessage {
    return msg.role === 'assistant';
}

// 使用
if (isAssistantMessage(msg)) {
    console.log(msg.content);  // msg 自动收窄为 AssistantMessage
}

第四天:Node.js 生态(2 小时)

4.1 模块系统

// ESM (ES Modules) —— Example Code 主用
// 每个 .ts 文件就是一个模块。没有显式 export 的都是私有的。

import { Tool, buildTool } from './Tool.js';        // 命名导入(注意写成 .js 不是 .ts)
import type { Message } from './types/message.js';   // type import —— 编译后完全消失
import memoize from 'lodash-es/memoize.js';          // 默认导入
import * as React from 'react';                      // 全部导入到命名空间

export const foo = 1;                                // 命名导出
export default class Main {};                        // 默认导出(一个模块最多一个)

// require —— CommonJS(遗留,少数文件用)
const { Tool } = require('./Tool.js');               // 等同于 import
module.exports = { foo };                            // 等同于 export
// 为什么源码中两种都用?require 可以放在 if 里做条件加载,import 只能放在顶层

4.2 package.json —— 项目清单

{
  "name": "Example-code",               // 包名
  "version": "1.0.0",
  "type": "module",                    // 关键:启用 ESM
  "bin": { "Example": "./dist/main.js" }, // CLI 入口
  "scripts": {
    "build": "bun build src/main.tsx --outdir=dist",  // npm run build 执行这个
    "typecheck": "tsc --noEmit"                        // 只检查类型
  },
  "dependencies": { ... },             // 运行时依赖
  "devDependencies": { ... }           // 仅开发时依赖
}

4.3 Bun 特性

// Bun 是 Example Code 的运行时 + 打包器(类似 Java 的 JDK + Gradle 合一)

// feature() —— 编译时条件,类似 C++ #ifdef
import { feature } from 'bun:bundle';

// 关键:feature() 在构建时求值。如果是 false,整个 require 分支被 tree-shake
const workflowTool = feature('WORKFLOW_SCRIPTS')
    ? require('./WorkflowTool.js').WorkflowTool  // feature 为 true: 包含这个代码
    : null;                                       // feature 为 false: 这段代码不出现在 dist 中

// 这就是"死代码消除" —— 未使用的功能不会打进最终 bundle

第五-六天:React + Ink 速成(4 小时)

5.1 核心心智模型

UI = f(state)    // 牢记这个公式

Android Compose  → React
─────────────────────────────────
@Composable        function Component() {
fun MyScreen() {     return <Box>...</Box>
    ...            }
}

mutableStateOf     useState()
LaunchedEffect     useEffect()
remember {}        useMemo() / useRef()

5.2 JSX —— 在 JS 中写 UI

// JSX 看起来像 HTML,但实际上是函数调用的语法糖
<Box flexDirection="column">
    <Text color="red">Hello</Text>
</Box>

// 等价于:
React.createElement(Box, { flexDirection: "column" },
    React.createElement(Text, { color: "red" }, "Hello")
);

// {} 嵌入 JS 表达式
<Text>找到 {count} 个文件</Text>

// 条件渲染
{error && <Text color="red">{error}</Text>}
{status === 'loading' ? <Spinner /> : <Content />}

// 列表渲染 —— 必须有 key
{tools.map(tool => (
    <Text key={tool.name}>{tool.name}</Text>
))}

5.3 useState —— 组件内部状态

function Counter() {
    //            getter       setter         初始值
    const [count,    setCount] = useState(0);

    return (
        <Box>
            <Text>计数: {count}</Text>
            {/* 点击时更新状态 → 组件自动重渲染 */}
            <Text onClick={() => setCount(count + 1)}>+1</Text>
        </Box>
    );
}

// Java Compose 等价:
// var count by remember { mutableStateOf(0) }
// Text("计数: $count")
// Text("+1", modifier = Modifier.clickable { count++ })

5.4 useEffect —— 副作用处理

function FileWatcher({ path }: { path: string }) {
    useEffect(() => {
        // 组件挂载时执行(类似 Android onStart / Compose LaunchedEffect)
        const watcher = startWatching(path);

        // 返回清理函数 —— 组件卸载时执行(类似 onStop / DisposableEffect.onDispose)
        return () => watcher.stop();
    }, [path]);  // path 变化时重新执行(类似 LaunchedEffect(path))
}

// 三种变体:
useEffect(() => { ... });        // 每次渲染后都执行
useEffect(() => { ... }, []);    // 仅在挂载时执行一次
useEffect(() => { ... }, [a,b]); // a 或 b 变化时执行

5.5 其他常用 Hook

// useRef —— 跨渲染保持引用(不触发重渲染)
const countRef = useRef(0);
countRef.current++;  // 修改不触发重渲染

// useMemo —— 缓存计算结果
const expensive = useMemo(() => computeHeavy(a, b), [a, b]);

// useCallback —— 缓存函数引用(避免不必要的子组件重渲染)
const handler = useCallback((x: number) => doSomething(x), [doSomething]);

// 自定义 Hook —— 以 use 开头,封装可复用逻辑
function useTheme() {
    const { theme } = useContext(ThemeContext);
    return theme;
}

5.6 源码中识别组件的技巧

// 遇到一个 .tsx 文件时,快速定位:

// 1. 顶层 export function 通常是一个组件
export function PermissionDialog({ request }: Props) {
    // ...
    return <Box>...</Box>;
}

// 2. 内部定义的 function 可能是子组件或 Hook
function usePermissionState() { ... }    // Hook(以 use 开头)
function renderOption(opt: Option) { ... } // 子渲染函数

// 3. 关注 return 语句
//    - return <Box>...</Box>  → 这是组件
//    - return { data: result } → 这不是组件,是普通函数

5.7 Ink 特有的组件替换

// Ink 把 React 的 DOM 元素替换为终端组件

// Web React                → Ink (Example Code)
// <div>                    → <Box>
// <span>                   → <Text>
// <input>                  → useInput() Hook
// onClick                   → Ink 没有鼠标,用键盘事件
// CSS                       → 组件 props (color, bold, ...)

import { Box, Text, useInput } from './ink.js';

function MyComponent() {
    useInput((input, key) => {
        if (key.escape) { /* Esc */ }
        if (key.return) { /* Enter */ }
        // key.tab, key.upArrow, key.downArrow, key.ctrl, key.meta ...
    });

    return (
        <Box flexDirection="row" padding={1}>
            <Text bold color="cyan">标题</Text>
            <Text dimColor>副文本</Text>
        </Box>
    );
}

第七天:Example Code 核心模式(3 小时)

6.1 Zod —— 运行时 Schema + 类型推导

import { z } from 'zod/v4';

// 定义 schema —— 这是运行时可用的数据结构描述
const FileReadInput = z.object({
    file_path: z.string().describe('要读取的文件路径'),
    offset: z.number().optional().describe('起始行'),
    limit: z.number().optional().describe('读取行数'),
});

// 从 schema 推导 TypeScript 类型 —— 核心魔法
type FileReadInput = z.infer<typeof FileReadInput>;
// 等价于手写:
// type FileReadInput = {
//     file_path: string;
//     offset?: number;
//     limit?: number;
// }

// 在 Tool 中使用
const ReadTool: Tool<typeof FileReadInput, string> = {
    inputSchema: FileReadInput,    // 运行时验证
    async call(input: FileReadInput, context) {
        // input 有完整类型 —— 编译时安全
        return readFile(input.file_path);
    }
};

6.2 buildTool —— 默认值注入

// 理解:不是所有工具都要实现 20+ 个方法
// buildTool 给未实现的方法填上安全默认值

const DEFAULTS = {
    isEnabled: () => true,            // 默认启用
    isConcurrencySafe: () => false,   // 默认不安全(保守)
    isReadOnly: () => false,          // 默认有写操作
    isDestructive: () => false,       // 默认不具破坏性
    checkPermissions: (input) => ({ behavior: 'allow', updatedInput: input }),
};

function buildTool(def) {
    return { ...DEFAULTS, ...def };  // def 中的方法覆盖 DEFAULTS
}

// 使用:
const ReadTool = buildTool({
    name: 'Read',
    inputSchema: FileReadInput,
    isReadOnly: () => true,  // 覆盖默认值
    async call(input, ctx) { ... },
    // 没有实现的 isConcurrencySafe 自动得到 DEFAULTS 中的 false
});

6.3 状态管理 —— useSyncExternalStore 模式

// Example Code 不用 Redux/MobX。它自己实现了一个简单的 Observable Store。
// 理解这个模式就够了,不需要知道实现细节。

// 创建 Store
const store = createStore({ messages: [], verbose: false });

// 订阅 —— 只在 selected 的值发生变化时重渲染
function useAppState<T>(selector: (state: AppState) => T): T {
    return useSyncExternalStore(
        store.subscribe,                          // 订阅变更
        () => selector(store.getState()),         // 获取当前值
    );
}

// 使用
function MyComponent() {
    // 只订阅 messages,verbose 变化不会触发重渲染
    const messages = useAppState(s => s.messages);

    // 获取 setter(不订阅任何状态,永不重渲染)
    const setState = useSetAppState();

    return (
        <Box>
            {messages.map(m => <MessageRow key={m.id} msg={m} />)}
            <Text onClick={() => setState(s => ({ ...s, verbose: !s.verbose }))}>
                切换详细模式
            </Text>
        </Box>
    );
}

6.4 工具注册和条件加载

// src/tools.ts —— 理解这个函数就读懂了工具系统的入口

function getAllBaseTools(): Tools {
    return [
        AgentTool,                     // 始终包含
        BashTool,                      // 始终包含
        FileReadTool,                  // 始终包含
        FileEditTool,                  // 始终包含

        // 条件包含(编译时)
        ...(feature('WORKFLOW_SCRIPTS') ? [WorkflowTool] : []),
        // 如果 WORKFLOW_SCRIPTS 特性开启 → 数组中有 WorkflowTool
        // 否则 → 空数组展开后什么都没有

        // 条件包含(运行时)
        ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),

        // 平台检测
        getPowerShellTool(),           // Windows 返回工具,其他平台返回 null
    ].filter(t => t !== null);        // 过滤掉 null
}

6.5 错误处理模式

// 源码中的典型错误处理

// 1. try-catch 包装异步操作
async function callTool(tool: Tool, input: unknown) {
    try {
        const result = await tool.call(input, context);
        return { ok: true, result };
    } catch (err) {
        return { ok: false, error: String(err) };
    }
}

// 2. 提前返回 guard 模式
async function processMessage(msg: Message) {
    if (msg.type !== 'user') return;           // guard
    if (!msg.content.trim()) return;           // guard
    // 主逻辑...
}

// 3. .catch 链式处理
fetchData()
    .then(processData)
    .catch(logAndReject)
    .catch(fallbackToDefault);

// 4. 条件抛出
if (!isEnabled()) throw new Error('Tool disabled');

实战:用学到的知识读一段源码

下面是从 src/tools/FileReadTool/FileReadTool.ts 摘录的精简版,看看能读懂多少:

// 1. 导入:ESM 模块导入,type import 编译后消失
import { buildTool, type Tool } from '../../Tool.js';
import { z } from 'zod/v4';

// 2. Zod schema:定义运行时数据结构
const FileReadInput = z.object({
    file_path: z.string().describe('Absolute path to the file'),
    offset: z.number().optional().describe('Line number to start reading from'),
    limit: z.number().optional().describe('Number of lines to read'),
});

// 3. 泛型:Tool<InputType, OutputType>
export const FileReadTool: Tool<typeof FileReadInput, string> = buildTool({
    name: 'Read',                    // 工具名
    inputSchema: FileReadInput,      // 输入 schema

    // 4. 箭头函数:() => boolean
    isReadOnly: () => true,          // Read 是只读的
    isConcurrencySafe: () => true,   // Read 可以并发

    // 5. async 函数:返回 Promise<ToolResult<string>>
    async call(input, context) {
        // 6. 解构:从 input 提取字段
        const { file_path, offset, limit } = input;

        // 7. await:等待文件读取完成
        const content = await readFile(file_path, { offset, limit });

        // 8. 返回:ToolResult 格式
        return {
            data: content,           // data 是 string 类型(由泛型约束)
        };
    },

    // 9. renderToolUseMessage:渲染工具使用预告
    renderToolUseMessage(input) {
        const path = input.file_path ?? '...';  // ?? 提供默认值
        // 10. JSX:返回 React 节点
        return <Text dimColor>Reading {path}</Text>;
    },
});

逐行翻译成 Java 思维:

// 如果这是 Java + Compose:

@Serializable
data class FileReadInput(
    val file_path: String,
    val offset: Int? = null,
    val limit: Int? = null,
)

class FileReadTool : Tool<FileReadInput, String> {
    override val name = "Read"
    override val inputSchema = FileReadInput.serializer()
    override fun isReadOnly() = true
    override fun isConcurrencySafe() = true

    override suspend fun call(input: FileReadInput, context: ToolContext): ToolResult<String> {
        val (filePath, offset, limit) = input  // Kotlin 解构
        val content = readFile(filePath, offset, limit)
        return ToolResult(content)
    }

    @Composable
    override fun RenderToolUse(input: Partial<FileReadInput>) {
        Text("Reading ${input.file_path ?: "..."}", color = Color.Gray)
    }
}

快速查阅卡

遇到不认识的语法时:

// 看到这个           → 这是
const { x } = obj;     → 解构:取 obj.x
const [a, b] = arr;    → 解构:取 arr[0], arr[1]
[...arr, item]         → 展开:新数组 = 所有旧元素 + item
{ ...obj, key: val }   → 展开合并:新对象覆盖 key
x?.y?.z                → 可选链:x 或 y 为 null/undefined 时返回 undefined
x ?? "default"         → 空值合并:x 为 null/undefined 时才用 default
x || "default"         → 逻辑或:x 为任何假值(0、""、false)时用 default
<T>(x: T): T           → 泛型函数
z.infer<typeof S>      → Zod:从 schema 推导 TS 类型
await f()               → 异步等待:暂停当前函数,拿到 Promise 的结果
...feature('X') ? [...] : [] → 条件加载:编译时 tree-shake

文件后缀含义:

.ts    → TypeScript(纯逻辑)
.tsx   → TypeScript + JSX(React 组件)
.js    → JavaScript(或手写的 JS 模块)
.json  → 数据文件
.md    → Markdown 文档

学习检查:如果你能流畅阅读上面第 7 节的示例代码并理解每一行在做什么,就具备了看懂 Example Code 源码的能力。接下来进入阶段 5,从 src/tools/FileReadTool/FileReadTool.ts 开始实际阅读源码。