生命周期和清理
OpenTUI 让你控制终端清理。在关闭时调用 renderer.destroy()。它会将终端恢复到原始状态并释放资源。
为什么必须处理清理
OpenTUI 不会在 process.exit 或未处理的错误时自动清理。这种设计让你对关闭行为有更多控制:
- 你可能想要处理错误并继续运行
- 你可能使用效果系统(如 Effect.ts),它们有自己的关闭处理
- 你可能需要自定义清理顺序或额外的关闭逻辑
使用 renderer.destroy()
在应用退出时调用 destroy():
import { createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer()
// ... 你的应用代码 ...
// 干净关闭
renderer.destroy()
为了可靠的清理,请将渲染器销毁保持在与应用启动相同的控制流中:
import { createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer()
try {
// ... 你的应用代码 ...
} finally {
renderer.destroy()
}
信号处理
OpenTUI 监听常见的退出信号并在接收到它们时调用 destroy()。默认情况下,它处理以下信号:
| 信号 | 说明 |
|---|---|
SIGINT | Ctrl+C |
SIGTERM | 终止信号 |
SIGQUIT | Ctrl+\ |
SIGABRT | 中止信号 |
SIGHUP | 挂起(终端关闭) |
SIGBREAK | Windows 上的 Ctrl+Break |
SIGPIPE | 管道断裂 |
SIGBUS | 总线错误 |
你可以自定义哪些信号触发清理:
// 仅处理 SIGINT 和 SIGTERM
const renderer = await createCliRenderer({
exitSignals: ["SIGINT", "SIGTERM"],
})
// 禁用所有基于信号的清理(自行处理)
const renderer = await createCliRenderer({
exitSignals: [],
})
Ctrl+C 行为
默认情况下,Ctrl+C 调用 destroy()。如果你想自己处理 Ctrl+C,请禁用内部处理器并从 exitSignals 中移除 SIGINT:
const renderer = await createCliRenderer({
exitOnCtrlC: false,
exitSignals: ["SIGTERM", "SIGQUIT", "SIGABRT", "SIGHUP", "SIGBREAK", "SIGPIPE", "SIGBUS"],
})
renderer.keyInput.on("keypress", (key) => {
if (key.ctrl && key.name === "c") {
// 自定义 Ctrl+C 处理
console.log("Ctrl+C pressed, but not exiting")
}
})
销毁回调
在渲染器销毁时运行自定义逻辑:
const renderer = await createCliRenderer({
onDestroy: () => {
console.log("Renderer destroyed, performing additional cleanup...")
},
})
destroy() 清理的内容
destroy() 方法清理以下资源:
- 移除 OpenTUI 添加的信号和进程监听器
- 清除计时器和渲染循环
- 销毁树中的所有 Renderable
- 恢复 stdin 原始模式
- 重置终端状态(光标、备用屏幕等)
- 刷新待处理的 split-footer 捕获输出
- 释放原生资源
自定义流会话
对于 socket、SSH 或 pty 会话,为每个连接提供自己的渲染器。在关闭传输之前销毁它:
import { createCliRenderer, type CliRenderer } from "@opentui/core"
interface TerminalSession {
renderer: CliRenderer
closeTransport: () => void
}
async function startSession(
stdin: NodeJS.ReadStream,
stdout: NodeJS.WriteStream,
closeTransport: () => void,
): Promise<TerminalSession> {
const renderer = await createCliRenderer({
stdin,
stdout,
width: stdout.columns || 80,
height: stdout.rows || 24,
exitOnCtrlC: false,
exitSignals: [],
})
return { renderer, closeTransport }
}
async function closeSession(session: TerminalSession): Promise<void> {
session.renderer.destroy()
await new Promise<void>((resolve) => queueMicrotask(resolve))
session.closeTransport()
}
当长时间运行的服务器进程拥有多个渲染器会话时,请使用 exitOnCtrlC: false 和 exitSignals: []。stdin 或 stdout 流不能被多个活动渲染器共享;destroy() 会释放它。
故障排除
如果你的终端在崩溃后保持异常状态:
- 在终端中运行
reset来恢复它 - 向应用添加
uncaughtException和unhandledRejection处理器 - 确保你在所有退出路径中调用
renderer.destroy()