React 绑定
使用 React 熟悉的模式和组件构建终端用户界面。
安装
使用 bun 和 create-tui 快速开始:
bun create tui --template react
手动安装:
bun install @opentui/react @opentui/core react
快速开始
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
function App() {
return <text>Hello, world!</text>
}
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
TypeScript 配置
配置你的 tsconfig.json:
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "@opentui/react",
"strict": true,
"skipLibCheck": true
}
}
运行时加载插件支持(如需要)
如果你的应用在运行时加载外部 TS/TSX 模块(例如基于文件的插件系统),请在动态导入之前在应用入口处导入一次:
import "@opentui/react/runtime-plugin-support"
此配置同时适用于常规 Bun 运行和独立编译的可执行文件。
组件
OpenTUI React 提供了映射到核心 Renderable 的 JSX 内置元素:
布局与显示
<text>- 带样式的文本显示<box>- 带边框和布局的容器<scrollbox>- 可滚动容器<ascii-font>- ASCII 艺术文本
二维码支持来自 @opentui/qrcode/react,必须通过 registerQRCode() 显式注册。
输入
<input>- 单行文本输入<textarea>- 多行文本输入<select>- 选择列表<tab-select>- 基于标签的选择
代码与差异
<code>- 语法高亮代码<line-number>- 支持差异/诊断的行号<diff>- 统一或分栏差异查看器<markdown>- Markdown 渲染
文本修饰器
在 <text> 组件内使用:
<span>- 内联样式文本<strong>、<b>- 粗体文本<em>、<i>- 斜体文本<u>- 下划线文本<br>- 换行<a>- 链接文本
API 参考
createRoot(renderer)
创建一个 React 根节点,用于渲染到终端。
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
Hooks
useRenderer()
获取 OpenTUI 渲染器实例。
import { useRenderer } from "@opentui/react"
import { useEffect } from "react"
function App() {
const renderer = useRenderer()
useEffect(() => {
renderer.console.show()
console.log("Hello from console!")
}, [])
return <box />
}
useKeyboard(handler, options?)
处理键盘事件。
import { useKeyboard, useRenderer } from "@opentui/react"
function App() {
const renderer = useRenderer()
useKeyboard((key) => {
if (key.name === "escape") {
renderer.destroy()
}
})
return <text>Press ESC to close</text>
}
处理按键释放事件:
useKeyboard(
(event) => {
if (event.eventType === "release") {
console.log("Key released:", event.name)
} else {
console.log("Key pressed:", event.name)
}
},
{ release: true },
)
useOnResize(callback)
处理终端窗口大小调整事件。
import { useOnResize } from "@opentui/react"
function App() {
useOnResize((width, height) => {
console.log(`Resized to ${width}x${height}`)
})
return <text>Resize-aware component</text>
}
useTerminalDimensions()
获取响应式终端尺寸。
import { useTerminalDimensions } from "@opentui/react"
function App() {
const { width, height } = useTerminalDimensions()
return (
<text>
Terminal: {width}x{height}
</text>
)
}
usePaste(handler)
处理终端粘贴事件(括号粘贴模式)。
import { decodePasteBytes } from "@opentui/core"
import { usePaste } from "@opentui/react"
function App() {
usePaste((event) => {
const text = decodePasteBytes(event.bytes)
console.log("Pasted text:", text)
})
return <text>Paste something into the terminal</text>
}
useFocus(handler)
订阅终端窗口获得焦点事件。
import { useFocus } from "@opentui/react"
function App() {
useFocus(() => {
console.log("Terminal gained focus")
})
return <text>Focus-aware component</text>
}
useBlur(handler)
订阅终端窗口失去焦点事件。
import { useBlur } from "@opentui/react"
function App() {
useBlur(() => {
console.log("Terminal lost focus")
})
return <text>Blur-aware component</text>
}
useSelectionHandler(handler)
处理文本选择事件(例如鼠标拖拽选择)。
import { useSelectionHandler } from "@opentui/react"
function App() {
useSelectionHandler((selection) => {
const text = selection.getSelectedText()
console.log("Selected:", text)
})
return <text selectable>Select this text with your mouse</text>
}
useTimeline(options?)
创建和管理动画。
import { useTimeline } from "@opentui/react"
import { useEffect, useState } from "react"
function App() {
const [width, setWidth] = useState(0)
const timeline = useTimeline({
duration: 2000,
loop: false,
})
useEffect(() => {
timeline.add(
{ width },
{
width: 50,
duration: 2000,
ease: "linear",
onUpdate: (animation) => {
setWidth(animation.targets[0].width)
},
},
)
}, [])
return <box style={{ width, backgroundColor: "#6a5acd" }} />
}
选项:
duration- 动画持续时间,单位为毫秒(默认:1000)loop- 是否循环(默认:false)autoplay- 自动开始(默认:true)onComplete- 完成回调onPause- 暂停回调
样式
通过属性或 style 属性为组件设置样式:
// 直接使用属性
<box backgroundColor="blue" padding={2}>
<text>Hello</text>
</box>
// 使用 style 属性
<box style={{ backgroundColor: "blue", padding: 2 }}>
<text>Hello</text>
</box>
示例:登录表单
import { createCliRenderer } from "@opentui/core"
import { createRoot, useKeyboard } from "@opentui/react"
import { useCallback, useState } from "react"
function App() {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [focused, setFocused] = useState<"username" | "password">("username")
const [status, setStatus] = useState("idle")
useKeyboard((key) => {
if (key.name === "tab") {
setFocused((prev) => (prev === "username" ? "password" : "username"))
}
})
const handleSubmit = useCallback(() => {
if (username === "admin" && password === "secret") {
setStatus("success")
} else {
setStatus("error")
}
}, [username, password])
return (
<box style={{ border: true, padding: 2, flexDirection: "column", gap: 1 }}>
<text fg="#FFFF00">Login Form</text>
<box title="Username" style={{ border: true, width: 40, height: 3 }}>
<input
placeholder="Enter username..."
onInput={setUsername}
onSubmit={handleSubmit}
focused={focused === "username"}
/>
</box>
<box title="Password" style={{ border: true, width: 40, height: 3 }}>
<input
placeholder="Enter password..."
onInput={setPassword}
onSubmit={handleSubmit}
focused={focused === "password"}
/>
</box>
<text fg={status === "success" ? "green" : status === "error" ? "red" : "#999"}>{status.toUpperCase()}</text>
</box>
)
}
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
组件扩展
将自定义 Renderable 注册为 JSX 元素:
import { BoxRenderable, createCliRenderer, type BoxOptions, type RenderContext } from "@opentui/core"
import { createRoot, extend } from "@opentui/react"
class ConsoleButtonRenderable extends BoxRenderable {
private _label: string = "Button"
constructor(ctx: RenderContext, options: BoxOptions & { label?: string }) {
super(ctx, options)
if (options.label) this._label = options.label
this.borderStyle = "single"
this.padding = 2
}
get label(): string {
return this._label
}
set label(value: string) {
this._label = value
this.requestRender()
}
}
// 添加 TypeScript 支持
declare module "@opentui/react" {
interface OpenTUIComponents {
consoleButton: typeof ConsoleButtonRenderable
}
}
// 注册组件
extend({ consoleButton: ConsoleButtonRenderable })
// 在 JSX 中使用
function App() {
return <consoleButton label="Click me!" style={{ border: true, backgroundColor: "green" }} />
}
const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
React DevTools
OpenTUI React 支持使用 React DevTools 进行调试:
- 安装:
bun add --dev react-devtools-core@7
- 启动 DevTools:
npx react-devtools@7
- 使用 DEV 标志运行:
DEV=true bun run your-app.ts