# @anthropic/ink 使用文档 > 本项目自定义的终端 React 渲染框架,基于 React 19 + react-reconciler + Yoga 布局引擎。 > **不是** npm 上的 `ink` 官方库,API 有大量扩展和差异。 --- ## 目录 1. [架构概览](#1-架构概览) 2. [快速开始](#2-快速开始) 3. [渲染 API](#3-渲染-api) 4. [组件体系](#4-组件体系) - [双层组件设计](#双层组件设计) - [Box / ThemedBox](#box--themedbox) - [Text / ThemedText](#text--themedtext) - [ScrollBox](#scrollbox) - [Button](#button) - [其他基础组件](#其他基础组件) - [设计系统组件](#设计系统组件) 5. [布局系统 (Styles)](#5-布局系统-styles) 6. [Hooks](#6-hooks) 7. [快捷键系统](#7-快捷键系统) 8. [主题系统](#8-主题系统) 9. [事件系统](#9-事件系统) 10. [工具函数](#10-工具函数) 11. [与官方 Ink 的关键差异](#11-与官方-ink-的关键差异) 12. [最佳实践](#12-最佳实践) --- ## 1. 架构概览 ``` packages/@ant/ink/src/ ├── core/ # Layer 1: 渲染引擎 │ ├── events/ # 事件系统 (InputEvent, ClickEvent, FocusEvent...) │ ├── layout/ # Yoga flexbox 布局 │ ├── termio/ # 终端 I/O (ANSI 解析/输出) │ ├── renderer.ts # 渲染器 (双缓冲 + diff) │ ├── reconciler.ts # React reconciler │ └── styles.ts # 样式类型定义 ├── components/ # Layer 2: UI 基础组件 (无主题) │ ├── Box.tsx # BaseBox │ ├── Text.tsx # BaseText │ ├── ScrollBox.tsx # 滚动容器 │ ├── Button.tsx # 按钮 │ └── ... ├── theme/ # Layer 3: 主题 + 设计系统 │ ├── ThemeProvider.tsx │ ├── ThemedBox.tsx # 主题感知的 Box │ ├── ThemedText.tsx # 主题感知的 Text │ ├── Dialog.tsx # 对话框 │ ├── Tabs.tsx # 标签页 │ └── ... ├── hooks/ # React hooks ├── keybindings/ # 快捷键系统 └── index.ts # 统一导出 ``` **三层架构**: - **core** — React reconciler + Yoga 布局 + 双缓冲渲染 + 终端 I/O - **components** — 原始 UI 组件 (无主题色,只接受 raw Color) - **theme** — 主题感知组件 + 设计系统 (接受 Theme key 作为颜色) --- ## 2. 快速开始 ```tsx import { wrappedRender as render, Box, Text, useApp, useInput, } from '@anthropic/ink' function App() { const { exit } = useApp() useInput((input, key) => { if (input === 'q') exit() }) return ( Hello Terminal! 按 q 退出 ) } // 渲染 const { unmount, waitUntilExit } = render() await waitUntilExit() ``` **注意**: 项目中统一使用 `wrappedRender`(导出时别名为 `render`),而非 `renderSync`。 --- ## 3. 渲染 API ### `wrappedRender(node, options?)` 异步渲染,返回 `Promise`: ```ts type Instance = { rerender: (node: ReactNode) => void // 重新渲染 unmount: () => void // 卸载 waitUntilExit: () => Promise // 等待退出 cleanup: () => void // 清理 } type RenderOptions = { stdout?: NodeJS.WriteStream // 默认 process.stdout stdin?: NodeJS.ReadStream // 默认 process.stdin stderr?: NodeJS.WriteStream // 默认 process.stderr exitOnCtrlC?: boolean // 默认 true patchConsole?: boolean // 默认 true onFrame?: (event: FrameEvent) => void } ``` ### `createRoot(options?)` 创建可复用的渲染根(类似 react-dom 的 `createRoot`): ```ts type Root = { render: (node: ReactNode) => void unmount: () => void waitUntilExit: () => Promise } const root = createRoot({ stdout: process.stdout }) root.render() // 后续可多次调用 root.render() 切换界面 ``` --- ## 4. 组件体系 ### 双层组件设计 本框架的组件分两层: | 层级 | 导出名 | 颜色类型 | 用途 | |------|--------|----------|------| | Base | `BaseBox`, `BaseText` | `Color` (rgb/hex/ansi) | 底层组件,无主题依赖 | | Themed | `Box`, `Text` | `keyof Theme \| Color` | **日常使用的组件**,自动解析主题色 | **日常开发请始终使用 `Box` / `Text`(Themed 版本)**,它们是默认导出。 --- ### Box / ThemedBox 终端里的 `
`,是最核心的布局容器。 ```tsx import { Box, Text } from '@anthropic/ink' // 基础布局 左侧 右侧 // 主题色边框 带主题色边框的盒子 // 键盘交互 { if (e.key === 'escape') e.preventDefault() }} > 可聚焦的盒子 ``` **Props**: | 属性 | 类型 | 说明 | |------|------|------| | `ref` | `Ref` | DOM 元素引用 | | `tabIndex` | `number` | Tab 键序(≥0 参与循环,-1 仅程序聚焦) | | `autoFocus` | `boolean` | 挂载时自动聚焦 | | `onClick` | `(e: ClickEvent) => void` | 鼠标点击(仅 AlternateScreen 内生效) | | `onFocus` / `onBlur` | `(e: FocusEvent) => void` | 焦点事件 | | `onFocusCapture` / `onBlurCapture` | 同上 | 捕获阶段焦点事件 | | `onKeyDown` / `onKeyDownCapture` | `(e: KeyboardEvent) => void` | 键盘事件 | | `onMouseEnter` / `onMouseLeave` | `() => void` | 鼠标进出(仅 AlternateScreen) | | 所有 `Styles` 属性 | 见 [布局系统](#5-布局系统-styles) | | **ThemedBox 额外的颜色属性**: | 属性 | 类型 | 说明 | |------|------|------| | `borderColor` | `keyof Theme \| Color` | 边框颜色(接受主题 key) | | `borderTopColor` 等 | 同上 | 单边边框颜色 | | `backgroundColor` | `keyof Theme \| Color` | 背景颜色(接受主题 key) | --- ### Text / ThemedText 文本渲染组件。 ```tsx import { Text } from '@anthropic/ink' // 主题色文本 Claude 错误信息 次要信息(使用 inactive 色) // 原始色值 橙色 也是橙色 256色橙色 // 文本截断 很长的文本会被截断... // bold 和 dim 互斥(终端限制) 加粗文本 暗淡文本 // ❌ 不允许 ``` **Props**: | 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `color` | `keyof Theme \| Color` | — | 文字颜色 | | `backgroundColor` | `keyof Theme` | — | 背景色(仅接受主题 key) | | `bold` | `boolean` | — | 加粗(与 dim 互斥) | | `dim` | `boolean` | — | 暗淡(与 bold 互斥) | | `dimColor` | `boolean` | — | 使用主题 inactive 色着色(不与 bold 互斥) | | `italic` | `boolean` | — | 斜体 | | `underline` | `boolean` | — | 下划线 | | `strikethrough` | `boolean` | — | 删除线 | | `inverse` | `boolean` | — | 反转前景/背景 | | `wrap` | 见下方 | `'wrap'` | 文本换行/截断模式 | **wrap 模式**: | 值 | 说明 | |----|------| | `'wrap'` | 自动换行(默认) | | `'wrap-trim'` | 换行并去除行尾空格 | | `'end'` | 不换行,末尾省略 | | `'middle'` | 不换行,中间省略 | | `'truncate-end'` | 末尾截断 | | `'truncate'` | 同 truncate-end | | `'truncate-middle'` | 中间截断 | | `'truncate-start'` | 开头截断 | **`TextHoverColorContext`**: 为子树中未设置 `color` 的 Text 提供级联颜色。 ```tsx import { Text, TextHoverColorContext } from '@anthropic/ink' 这段文字会继承 suggestion 色 这段保持 error 色 ``` --- ### ScrollBox 支持虚拟滚动的容器,用于显示超出视口的内容。 ```tsx import { ScrollBox, Box, Text } from '@anthropic/ink' import { useRef } from 'react' function LogViewer({ lines }: { lines: string[] }) { const scrollRef = useRef(null) // 编程式滚动 useKeybinding('app:scrollUp', () => scrollRef.current?.scrollBy(-5)) useKeybinding('app:scrollDown', () => scrollRef.current?.scrollBy(5)) return ( {lines.map((line, i) => ( {line} ))} ) } ``` **ScrollBoxHandle 方法**: | 方法 | 返回 | 说明 | |------|------|------| | `scrollTo(y)` | `void` | 滚动到指定行 | | `scrollBy(dy)` | `void` | 相对滚动 | | `scrollToElement(el, offset?)` | `void` | 滚动到指定元素 | | `scrollToBottom()` | `void` | 滚到底部 | | `getScrollTop()` | `number` | 当前滚动位置 | | `getScrollHeight()` | `number` | 内容总高度 | | `getViewportHeight()` | `number` | 视口高度 | | `isSticky()` | `boolean` | 是否粘在底部 | | `setClampBounds(min, max)` | `void` | 限制滚动范围 | | `subscribe(listener)` | `() => void` | 订阅滚动变化 | **关键 Props**: - `stickyScroll` — 自动跟踪最新内容(适合日志/聊天) - 继承所有 `Styles` 属性(需设置 `flexGrow={1}` 或固定 `height`) --- ### Button 可交互按钮,支持键盘(Enter/Space)和鼠标点击。 ```tsx import { Button, Text } from '@anthropic/ink' function ConfirmButton({ onConfirm }: { onConfirm: () => void }) { return ( ) } ``` **Props**: | 属性 | 类型 | 说明 | |------|------|------| | `onAction` | `() => void` | 按下 Enter/Space/点击时触发 | | `tabIndex` | `number` | Tab 序号 | | `autoFocus` | `boolean` | 自动聚焦 | | `children` | `(state: ButtonState) => ReactNode \| ReactNode` | render prop 或静态内容 | **ButtonState**: `{ focused: boolean, hovered: boolean, active: boolean }` --- ### 其他基础组件 #### `Newline` ```tsx // 插入一个空行 // 插入三个空行 ``` #### `Spacer` ```tsx // 占据剩余空间 ``` #### `Link` — OSC 8 超链接 ```tsx 点击打开 ``` #### `NoSelect` — 禁止文本选择 ```tsx 这段文字无法被鼠标选中复制 ``` #### `RawAnsi` — 预渲染 ANSI 内容 ```tsx ``` #### `AlternateScreen` — 全屏 TUI 模式 ```tsx {/* 启用鼠标追踪、文本选择等高级功能 */} ``` --- ### 设计系统组件 这些是 theme 层提供的高级 UI 组件。 #### `Dialog` — 对话框 ```tsx import { Dialog, Box, Text } from '@anthropic/ink' 确定要继续吗?