Deep Dive | src/screens/REPL.tsx 5005 行 — Claude Code 主屏幕(God Component)¶
重要性:⭐⭐⭐⭐⭐(最大单文件 + UI 中心 + 50+ hooks 编排) 真实位置:
src/screens/REPL.tsx(5005 行) 角色:Claude Code 交互模式的根屏幕组件——把所有 hooks、状态、事件、UI 拼成单个 React 组件 关联:topics/deep-dive-main.md(main.tsx 的launchRepl入口)、topics/deep-dive-query-engine.md(消息循环)
1. 文件全景¶
REPL.tsx (5005 行)
│
├── 行 1-220 :imports + 多个 feature-gated 懒加载 hook(50+)
│
├── 行 221-310 :常量 + utility + stub(PROACTIVE_NO_OP_SUBSCRIBE 等)
│ ├── PROACTIVE_NO_OP_SUBSCRIBE (行 195)
│ ├── PROACTIVE_FALSE (行 196)
│ ├── SUGGEST_BG_PR_NOOP (行 197)
│ ├── RECENT_SCROLL_REPIN_WINDOW_MS (行 305)
│ └── median() (行 311)
│
├── 行 321-470 :TranscriptModeFooter (行 321) + TranscriptSearchBar (行 368)
│
├── 行 473-528 :AnimatedTerminalTitle (行 484) + _temp / _temp2 辅助
│
├── 行 530-570 :Props 类型 (行 526)
│
├── 行 572-4985 :**REPL 主组件**(~4413 行)— 文件主体
│
└── 行 4985-5005:AlternateScreen 包装 + 收尾
核心洞察:整个文件的 88% 都是一个 React 函数组件——REPL()。
2. 6 个独立函数¶
function median(values: number[]): number // 行 311
function TranscriptModeFooter(t0) // 行 321
function TranscriptSearchBar({...}) // 行 368
function AnimatedTerminalTitle(t0) // 行 484
function _temp2(setFrame_0) // 行 520
function _temp(f) // 行 523
export function REPL({...}) // 行 572 ← 主组件
6 个函数 中,4 个是辅助 / UI 子组件,REPL() 自己是 ~4413 行。
3. 60+ 唯一 Hook 列表(grep 验证)¶
REPL 组件没有拆分成小自定义 hook 之外,调用了 60+ 个 hook:
3.1 React 内置(4 个)¶
useStateuseEffectuseLayoutEffectuseMemouseCallbackuseRefuseDeferredValueuseSyncExternalStore(在 import 里)
3.2 全局状态(3 个)¶
useAppState—— AppStateStore selector(~20+ 次)useAppStateStore—— store 引用useSetAppState
3.3 业务 hooks(50+ 个)¶
会话/消息:
- useLogMessages —— 消息流 append
- useReplBridge —— Bridge 协议
- useAssistantHistory —— assistant 历史
- useCanUseTool —— 权限检查
- useQueueProcessor —— 队列处理
- useMessageActions —— 消息操作
- useDeferredHookMessages —— 延迟 hook 消息
IDE/远程:
- useIdeLogging / useIdeSelection / useIDEIntegration / useIDEStatusIndicator
- useRemoteSession / useDirectConnect / useSSHSession
输入/UI:
- useInput —— Ink 全局输入
- useSearchInput / useSearchHighlight —— transcript 搜索
- useTerminalSize
- useCommandQueue —— 命令队列
- useKeybindings 相关
通知/状态:
- useNotifications —— toast 通知
- useModelMigrationNotifications
- useDeprecationWarningNotification
- useNpmDeprecationNotification
- useAntOrgWarningNotification
- useRateLimitWarningNotification
- useFastModeNotification
- useAutoModeUnavailableNotification
- useMcpConnectivityStatus
- usePluginInstallationStatus
- usePluginAutoupdateNotification
- useSettingsErrors
- useLspInitializationNotification
- useTeammateLifecycleNotification
- useChromeExtensionNotification
- useOfficialMarketplaceNotification
- useInstallMessages
- useIssueFlagBanner
- useCanSwitchToExistingSubscription
Skills/Plugins:
- useSkillsChange —— 监听 skill 文件变更
- useManagePlugins
- useMergedClients / useMergedTools / useMergedCommands
proactive / agent:
- useProactive / useScheduledTasks
- useInboxPoller
- useMailboxBridge
安全:
- useKickOffCheckAndDisableBypassPermissionsIfNeeded
- useKickOffCheckAndDisableAutoModeIfNeeded
模型/认证:
- useMainLoopModel —— 当前主循环模型
- useApiKeyVerification
- useFileHistorySnapshotInit
- useSkillsChange
Voice / 输入扩展:
- useVoiceIntegration (feature('VOICE_MODE'))
- VoiceKeybindingHandler
Telemetry / Survey:
- useFpsMetrics —— FPS 监控
- useFeedbackSurvey
- useMemorySurvey
- useSkillImprovementSurvey
- usePostCompactSurvey
- useHint
- useCostSummary
- useAwaySummary
- useFrustrationDetection
- useClaudeCodeHintRecommendation
- useLspPluginRecommendation
- useMoreRight
其他:
- useAfterFirstRender
- useBackgroundTaskNavigation
- useSwarmInitialization
- useTeammateViewAutoExit
- useLogMessages (上面)
总 60+ 个 hook 调用——REPL 是编排中心而非业务实现。
4. 顶部:5 类懒加载(行 95-272)¶
// Voice mode (lazy)
const useVoiceIntegration: typeof import('../hooks/useVoiceIntegration.js').useVoiceIntegration =
feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({...});
const VoiceKeybindingHandler = feature('VOICE_MODE') ? require(...) : () => null;
// Proactive (lazy)
const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/index.js') : null;
const PROACTIVE_NO_OP_SUBSCRIBE = (_cb: () => void) => () => {};
const PROACTIVE_FALSE = () => false;
const SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
const useProactive = ... ? require('../proactive/useProactive.js').useProactive : null;
const useScheduledTasks = feature('AGENT_TRIGGERS') ? require(...) : null;
// ANT-ONLY
const AntModelSwitchCallout = "external" === 'ant' ? require(...) : null;
const shouldShowAntModelSwitch = "external" === 'ant' ? require(...) : (): boolean => false;
const UndercoverAutoCallout = "external" === 'ant' ? require(...) : null;
// Web browser tool (lazy)
const WebBrowserPanelModule = feature('WEB_BROWSER_TOOL') ? require('../tools/WebBrowserTool/WebBrowserPanel.js') as typeof import(...) : null;
5 类懒加载:
| 类别 | 触发条件 | 兜底 |
|---|---|---|
VOICE_MODE |
feature('VOICE_MODE') | 返回 noop |
PROACTIVE / KAIROS |
feature 二者之一 | null + noop stub |
AGENT_TRIGGERS |
feature('AGENT_TRIGGERS') | null |
| ANT-ONLY | "external" === 'ant' |
null / false |
WEB_BROWSER_TOOL |
feature | null |
3 种兜底模式:
- null —— module?.foo 调用
- () => ({...}) —— 返回 noop stub 对象
- () => null —— 返回 null 组件
这与 main.tsx 的 lazy require 模式一致——DCE + 运行时安全。
5. Props 接口(行 526-570)¶
export type Props = {
// 初始数据(来自 main.tsx / ResumeConversation)
commands: initialCommands,
debug,
initialTools,
initialMessages,
pendingHookMessages,
initialFileHistorySnapshots,
initialContentReplacements,
initialAgentName,
initialAgentColor,
mcpClients: initialMcpClients,
dynamicMcpConfig: initialDynamicMcpConfig,
// 模式开关
autoConnectIdeFlag,
strictMcpConfig = false,
systemPrompt: customSystemPrompt,
appendSystemPrompt,
// 回调
onBeforeQuery,
onTurnComplete,
// 状态
disabled = false,
mainThreadAgentDefinition: initialMainThreadAgentDefinition,
disableSlashCommands = false,
// 远程 / IDE 配置
taskListId,
remoteSessionConfig,
directConnectConfig,
sshSession,
thinkingConfig
};
~25 个 props,几乎所有数据都从外部传入(main.tsx 准备好),REPL 主要做编排而非初始化。
6. REPL() 主组件 6 段结构¶
REPL({...}) (行 572-4985, ~4413 行)
│
├── A. Hooks 初始化段(行 599-1140, ~540 行)— 50+ hooks 调用
│ ├── env-var 缓存(useMemo)
│ ├── useAppState selectors (~20 次)
│ ├── useEffect lifecycle
│ ├── useState (commands, screen, dumpMode, editor, IDE...)
│ ├── useNotifications
│ ├── useMergedClients/Tools/Commands
│ ├── IDE 状态
│ ├── 60+ 个 useXxx() 业务 hook
│ └── keybindings 设置
│
├── B. Refs + 派生数据段(行 1140-1700, ~560 行)— useRef + useMemo 派生
│
├── C. 事件处理器段(行 1700-2700, ~1000 行)— onSubmit, onExit, handleXxx
│
├── D. Query Engine 集成段(行 2700-3500, ~800 行)— useLogMessages, message 流
│
├── E. Render 前置段(行 3500-4900, ~1400 行)— 大量条件检查 + 状态合并
│
└── F. Render 段(行 4900-4985, ~85 行)— JSX 输出
7. A 段详解:50+ Hooks 编排¶
7.1 Env-var 缓存(行 603-608)¶
const titleDisabled = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE), []);
const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);
const disableMessageActions = feature('MESSAGE_ACTIONS') ?
useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_MESSAGE_ACTIONS'), []) : false;
注释解释:
Env-var gates hoisted to mount-time — isEnvTruthy does toLowerCase+trim+includes, and these were on the render path (hot during PageUp spam).
为什么用 useMemo([]) 而不是顶层 const:
- isEnvTruthy 做 toLowerCase+trim+includes(每次 ~微秒级)
- 在 PageUp 滚屏时频繁重渲染(每帧)
- 提到 mount-time 一次性计算
7.2 AppState selectors(行 617-640)¶
const [mainThreadAgentDefinition, setMainThreadAgentDefinition] = useState(initialMainThreadAgentDefinition);
const toolPermissionContext = useAppState(s => s.toolPermissionContext);
const verbose = useAppState(s => s.verbose);
const mcp = useAppState(s => s.mcp);
const plugins = useAppState(s => s.plugins);
const agentDefinitions = useAppState(s => s.agentDefinitions);
const fileHistory = useAppState(s => s.fileHistory);
const initialMessage = useAppState(s => s.initialMessage);
const queuedCommands = useCommandQueue();
// ... 20+ 个 selector
特点:每个字段单独 selector(不用 useAppState() 全量)——精确订阅,避免无关字段更新触发重渲染。
7.3 useEffect 局部副作用(行 611-614, 648-671)¶
// 1. 生命周期 logging
useEffect(() => {
logForDebugging(`[REPL:mount] REPL mounted, disabled=${disabled}`);
return () => logForDebugging(`[REPL:unmount] REPL unmounting`);
}, [disabled]);
// 2. Bootstrap local agent
useEffect(() => {
if (!viewingAgentTaskId || !needsBootstrap) return;
const taskId = viewingAgentTaskId;
void getAgentTranscript(asAgentId(taskId)).then(result => {
setAppState(prev => { ... });
});
}, [viewingAgentTaskId, needsBootstrap, setAppState]);
bootstrap local_agent 注释:
Bootstrap: retained local_agent that hasn't loaded disk yet → read sidechain JSONL and UUID-merge with whatever stream has appended so far. Stream appends immediately on retain (no defer); bootstrap fills the prefix. Disk-write-before-yield means live is always a suffix of disk.
关键:
- diskLoaded 标记
- live 是 disk 的后缀(disk-write-before-yield 不变量)
- 启动时 fill prefix(diskOnly = disk - live)
7.4 60+ useXxx 业务 hooks(行 740-1140)¶
这一段几乎全是 hook 调用——每个 hook 负责一个独立的"职责":
useModelMigrationNotifications();
useCanSwitchToExistingSubscription();
useIDEStatusIndicator({...});
useMcpConnectivityStatus({...});
useAutoModeUnavailableNotification();
usePluginInstallationStatus();
usePluginAutoupdateNotification();
useSettingsErrors();
useRateLimitWarningNotification(mainLoopModel);
useFastModeNotification();
useDeprecationWarningNotification(mainLoopModel);
useNpmDeprecationNotification();
useAntOrgWarningNotification();
useInstallMessages();
useChromeExtensionNotification();
useOfficialMarketplaceNotification();
useLspInitializationNotification();
useTeammateLifecycleNotification();
useHint();
useCostSummary();
useFeedbackSurvey();
useSkillImprovementSurvey();
useFpsMetrics();
useMemorySurvey();
usePostCompactSurvey();
useFrustrationDetection();
useClaudeCodeHintRecommendation();
useLspPluginRecommendation();
useMoreRight();
// ... 还有
模式: - 每个 hook 自包含全部逻辑(subscription、副作用、cleanup) - REPL 只需要调用它 - 关注点分离:hook 文件 ~50-300 行,REPL 只编排
这与"hooks-first 架构"一致——业务逻辑封装在 hooks,UI 组件保持精简。
8. C 段详解:事件处理器巨兽¶
事件处理器没有拆成子组件——所有 onSubmit、onExit、handleXxx 都在 REPL 内联。
8.1 onSubmit(推测行 1700-2200)¶
const onSubmit = useCallback(async (text: string, submitMode: InputMode) => {
// 1. trim / validate
// 2. handle slash commands
// 3. handle bash mode
// 4. handle agent invocation
// 5. call QueryEngine
// 6. handle result / error
// 7. update history
}, [/* 10+ deps */]);
可能 500 行——单 onSubmit 处理 7+ 种输入模式(slash command、bash、agent、raw prompt、paste、file 等)。
8.2 handleExit(推测行 2000)¶
优雅退出——保存状态、清理 subscriptions、记录 telemetry。
8.3 50+ onXxx¶
onAutoUpdaterResultonShowMessageSelectoronMessageActionsEnteronAgentSubmitonBackgroundSessiononRestoreCodeonSummarizeonRestoreMessageonPreRestore- ...
50+ 个事件处理器——都在 REPL 内。
9. D 段详解:Query Engine 集成¶
useLogMessages 是 REPL 与 QueryEngine 的桥梁:
- 提供 messages(已渲染列表)
- setMessages (追加)
- 内部调用 runQuery / queryModel
QueryEngine 的输出(流式 SSE 事件)→ useLogMessages 转换为 React state → REPL render。
10. E 段详解:Render 前置(1400 行)¶
这是最复杂的一段——1400 行全是条件检查 + 状态合并:
// 1. IDE 状态派生
const effectiveIdeSelection = ...;
const showIdeOnboarding = ...;
// 2. Tool list 派生
const tools = useMemo(() => mergedTools + dynamicTools, [...]);
const localTools = useMemo(() => getTools(...), [...]);
// 3. Message selector
const messageSelectorPreselect = ...;
// 4. 是否显示 transcript
const isShowingTranscript = screen === 'transcript';
// 5. 其他派生
const isLoading = ...;
const hasSuppressedDialogs = ...;
const focusedInputDialog = ...;
const cursor = ...;
E 段不返回 JSX——只准备所有 render 需要的"原料"。
11. F 段详解:Render(85 行)¶
const mainReturn = (
<KeybindingSetup>
<MCPConnectionManager>
<Box ...>
{isShowingTranscript ? (
<TranscriptView ... />
) : (
<>
{toolPermissionDialog && <ToolPermissionDialog ... />}
{/* 几十个 conditional UI */}
<PromptInput ... />
<SessionBackgroundHint ... />
</>
)}
{cursor && <MessageActionsBar ... />}
{focusedInputDialog === 'message-selector' && <MessageSelector ... />}
{feature('BUDDY') && companionVisible && <CompanionSprite />}
</Box>
</MCPConnectionManager>
</KeybindingSetup>
);
if (isFullscreenEnvEnabled()) {
return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
{mainReturn}
</AlternateScreen>;
}
return mainReturn;
3 层包装:
- <KeybindingSetup> —— 全局快捷键
- <MCPConnectionManager> —— MCP 连接状态
- <Box> —— Ink 布局
两个 render 路径:
- isFullscreenEnvEnabled()(真 TTY)→ <AlternateScreen> 包装(用备用屏幕 buffer,切换时不留痕迹)
- 否则直接返回
~85 行 JSX——但 props 极其庞大(每个子组件都有 20+ props)。
12. 关键设计模式¶
12.1 Hooks-first 架构¶
业务逻辑 100% 在 hooks 里,REPL 只做编排 + render。
对比传统 React:
- 传统:组件内 useState + useEffect + 业务函数
- 这里:组件内只调用 hooks,没有复杂 effect
12.2 局部 useState vs 全局 AppState¶
全局(AppStateStore): - 跨组件共享:toolPermissionContext、verbose、mcp、plugins、agentDefinitions - 状态机式:tasks、teamContext、elicitation
局部(useState): - 仅本组件用:screen、dumpMode、editorStatus、IDE 选择 - 临时:inputValue、vimMode
判断标准: - 多处需要 → AppState - 单组件用 → useState
12.3 Lazy hook 加载(行 95-272)¶
const useVoiceIntegration = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({...});
模式:
- feature('X') 编译时门控
- null / () => ({...}) 兜底
- 使用时 module?.foo 判空
效果:商业版不打包 voice 模块(~几 MB 减小)。
12.4 大量 notification hooks¶
useModelMigrationNotifications();
useDeprecationWarningNotification(mainLoopModel);
useNpmDeprecationNotification();
useAntOrgWarningNotification();
useInstallMessages();
usePluginInstallationStatus();
// ... 20+ 个
为什么这么多:每个通知都是独立 hook——单独订阅、单独条件显示、单独 dismiss。
12.5 useDeferredValue + useDeferredHookMessages¶
useDeferredValue(inputValue)—— 输入不阻塞渲染useDeferredHookMessages—— hook 输出延迟显示
性能优化:用户打字时不卡。
12.6 6 个顶层 helper 维持纯函数¶
median()—— 纯TranscriptModeFooter/TranscriptSearchBar/AnimatedTerminalTitle—— 展示组件_temp/_temp2—— 微优化辅助
REPL 自身不是纯组件——它有大量副作用。
13. 性能优化技巧¶
13.1 Env-var 提到 mount-time(行 603-608)¶
const titleDisabled = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE), []);
PageUp 滚屏时频繁重渲染——env-var 检查只跑一次。
13.2 AppState 精确 selector(行 617-640)¶
每个字段单独 useAppState(s => s.field)——只在该字段变更时重渲染。
对比:useAppState() 全量订阅 → 任何字段变都重渲染。
13.3 useDeferredValue(grep)¶
打字时:input 立即更新(受控),但昂贵的渲染用 deferredValue(低优先级)。
13.4 useMemo 包裹大计算¶
const localTools = useMemo(() => getTools(toolPermissionContext), [toolPermissionContext, proactiveActive, isBriefOnly]);
getTools 可能很重——只 3 个 dep 变化时重算。
13.5 useCallback 包裹事件处理器¶
50+ useCallback —— 防止子组件因 prop 引用变化无谓重渲染。
14. 与其他文件的关系¶
REPL.tsx
├──→ 60+ hooks/ 目录(业务逻辑封装)
│ ├── useLogMessages(QueryEngine 集成)
│ ├── useReplBridge(Bridge 协议)
│ ├── useIdeSelection / useIDEIntegration(IDE)
│ ├── useProactive(proactive 模式)
│ └── ...
│
├──→ state/AppStateStore(全局状态)
│
├──→ components/(UI 子组件)
│ ├── PromptInput
│ ├── MessageSelector
│ ├── MessageActionsBar
│ ├── CompanionSprite
│ └── ...
│
├──→ ink/(Ink 框架)
│ ├── Box / Text
│ ├── useInput
│ └── AlternateScreen
│
├──→ proactive/(proactive 模块,feature-gated)
├──→ voice/(voice 模式,feature-gated)
├──→ tools/WebBrowserTool/(feature-gated)
│
└──→ 20+ notification hooks
REPL 是"组装工厂"——它 import 一切,编排一切,渲染一切。
15. 复杂度分析¶
| 维度 | 数字 |
|---|---|
| 总行数 | 5005 |
| 函数数 | 6 |
| 唯一 hooks | 60+ |
useState 局部 |
15+ |
useAppState selector |
20+ |
useEffect / useLayoutEffect |
30+ |
useCallback / useMemo |
60+ |
| JSX 元素 | ~300 |
| Props 数 | 25 |
这是项目里最大的单文件——5005 行 React。
16. 设计哲学¶
16.1 "God Component" 的合理化¶
通常 God Component 是反模式。但 Claude Code 的选择是合理的:
- 避免 context 套娃——不用 Provider 链
- 避免 props drilling——50+ 子组件的 props 都从 REPL 来
- 生命周期集中——mount/unmount 在一处
- 调试方便——所有逻辑在一个文件
代价:5005 行的"瑞士军刀"。
16.2 Hooks-first 拆解复杂度¶
REPL 不实现业务——业务在 hooks。REPL 只编排:
- 50+ hook 调用
- 50+ useCallback
- 20+ useMemo
结果:单文件 5005 行,但单一职责:"把所有东西装到一起"。
16.3 严格分层¶
REPL 不应该 import 业务模块——只 import hooks 和 components。
17. 阅读建议¶
- 不要通读——会被 60+ hooks 淹没
- 从 Props 开始(行 526)——看 REPL 接收什么
- 看 A 段 50+ hooks 列表(行 599-1140)—— 知道它编排什么
- 跳到 F 段 render(行 4900-4985)—— 看输出
- 回头看 E 段前置状态(行 3500-4900)—— 理解 render 怎么组装
- 不要进 C/D 段(事件 + QueryEngine 集成)—— 那是 hooks 的细节
18. 关键洞察¶
18.1 单文件 5005 行的"可读性挑战"¶
- 主组件 4413 行
- 60+ hooks
- 50+ useCallback
- 20+ useState
项目用大量 section comment(// =====、// ---)分段——但单文件仍然巨大。
18.2 Hooks-first 架构的优势¶
- 业务可测:hooks 可独立测试
- 关注点分离:UI 编排 vs 业务实现
- 复用性强:hook 可被其他组件用
18.3 Env-var 性能优化的细节¶
isEnvTruthy 提到 mount-time(PageUp 滚屏优化)——这是真实性能瓶颈修复。
18.4 大量 notification hooks = UX 细节堆砌¶
20+ 个通知 hook —— 每个都对应一个 UX 细节(模型迁移、deprecation、npm 警告、ant org、插件更新、设置错误、速率限制、auto mode、MCP 连接、LSP 初始化、teammate 生命周期……)。
这是商业产品打磨的痕迹。
18.5 ANT-ONLY 和 feature-gated 散布全文¶
"external" === 'ant' 和 feature('X') 散布——编译时裁剪,不污染运行时。
18.6 useDeferredValue 是"打字不卡"的关键¶
用户每按一个键,REPL 重渲染——useDeferredValue 让渲染和输入解耦。
19. 与其他深度拆解的关系¶
| 文件 | 关系 |
|---|---|
main.tsx |
launchRepl 渲染 REPL |
replLauncher.ts |
提供 initial state + render |
state/AppStateStore.ts |
全局状态,REPL 通过 useAppState 订阅 |
hooks/useLogMessages.js |
消息流桥接 |
hooks/useReplBridge.js |
Bridge 协议 |
hooks/useXxx.js (50+) |
各业务领域封装 |
components/PromptInput.tsx |
REPL 的核心子组件(输入框) |
20. 阅读清单¶
- ✅ 看 Props 类型(行 526-570)—— 知道 REPL 接什么
- ✅ 数 60+ hooks(grep
use[A-Z]\w+() - ✅ 看 A 段 hooks 列表(行 599-1140)
- ✅ 看 F 段 render(行 4900-4985)
- 📌 跳看 1-2 个关键 hook 实现(
useLogMessages/useReplBridge) - 📌 对照 topics/deep-dive-main.md 理解
launchRepl怎么到这 - 📌 对照 topics/deep-dive-app-state-store.md 理解 AppStateStore
21. 练习任务¶
- 数 60+ hooks(grep + sort -u)—— 看清 REPL 编排的全貌
- 找 feature('X') 和 "external" === 'ant' —— 算 REPL 砍了多少代码
- 画 REPL 内部模块依赖图 —— 50+ hooks 分几类
- 找一个 useEffect —— 解读它清理函数(
return () => ...) - 思考:如果把 REPL 拆成 10 个小组件,会怎样?会不会更好?
- 手写迷你 REPL(~50 行)—— 1 个 useState + 1 个 useEffect + render