生命周期和清理

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()。默认情况下,它处理以下信号:

信号说明
SIGINTCtrl+C
SIGTERM终止信号
SIGQUITCtrl+\
SIGABRT中止信号
SIGHUP挂起(终端关闭)
SIGBREAKWindows 上的 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: falseexitSignals: []stdinstdout 流不能被多个活动渲染器共享;destroy() 会释放它。

故障排除

如果你的终端在崩溃后保持异常状态:

  1. 在终端中运行 reset 来恢复它
  2. 向应用添加 uncaughtExceptionunhandledRejection 处理器
  3. 确保你在所有退出路径中调用 renderer.destroy()