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开始实际阅读源码。
