# 第一阶段 · 模块一 · 第十七节:Screens 界面系统
什么是 Screens?Claude Code 有哪些主要界面?REPL、Doctor、Resume 等页面是如何实现的?如何自定义 Screen?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ UI 层(ui/) │
│ │
│ Screens ← 本节 │
│ ├── REPL 交互界面 │
│ ├── Doctor 诊断界面 │
│ ├── Resume 恢复界面 │
│ └── 自定义界面 │
└─────────────────────────────────────────────────────────────────────┘
源码位置:`src/ui/screens/`
Screen 是 Claude Code 的独立界面单元,每个 Screen 负责特定的功能或用户场景。
┌─────────────────────────────────────────────────────────────────────┐
│ Claude Code 界面 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ REPL │ │ Doctor │ │ Resume │ │
│ │ 主交互 │ │ 诊断 │ │ 恢复 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
// Screen 类型
type ScreenType =
| 'repl' // REPL 交互界面
| 'doctor' // 诊断界面
| 'resume' // 会话恢复界面
| 'onboarding' // 引导界面
| 'settings' // 设置界面
问 1:Screen 和 Component 有什么区别?
// Component:
// - 可复用的 UI 单元
// - 可以嵌入任何地方
// Screen:
// - 完整的页面
// - 包含多个 Component
// - 有独立路由/状态
源码位置:`src/ui/screens/ReplScreen.tsx`
// REPL 主界面
function ReplScreen() {
const [input, setInput] = useState('')
const [history, setHistory] = useState<Message[]>([])
const [isProcessing, setIsProcessing] = useState(false)
return (
<div className="repl-screen">
<HistoryPanel messages={history} />
<InputPanel
value={input}
onChange={setInput}
disabled={isProcessing}
/>
</div>
)
}
// 消息历史
interface Message {
id: string
role: 'user' | 'assistant' | 'system'
content: string
timestamp: Date
attachments?: Attachment[]
}
// 流式输出处理
function handleStreamChunk(chunk: string) {
// 1. 追加到当前消息
appendToCurrentMessage(chunk)
// 2. 实时渲染
requestAnimationFrame(() => {
scrollToBottom()
})
}
源码位置:`src/ui/screens/DoctorScreen.tsx`
// Doctor 诊断界面
function DoctorScreen() {
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([])
const [isRunning, setIsRunning] = useState(false)
const runDiagnostics = async () => {
setIsRunning(true)
const results = await runAllDiagnostics()
setDiagnostics(results)
setIsRunning(false)
}
return (
<div className="doctor-screen">
<DiagnosticHeader />
<DiagnosticList items={diagnostics} />
<RunButton onClick={runDiagnostics} loading={isRunning} />
</div>
)
}
// 诊断项
interface Diagnostic {
name: string
status: 'pass' | 'fail' | 'warning'
message: string
details?: string
fixAction?: () => void
}
源码位置:`src/ui/screens/ResumeScreen.tsx`
// Resume 恢复界面
function ResumeScreen() {
const [sessions, setSessions] = useState<Session[]>([])
const [selectedSession, setSelectedSession] = useState<Session | null>(null)
return (
<div className="resume-screen">
<SessionList
sessions={sessions}
onSelect={setSelectedSession}
/>
<SessionPreview session={selectedSession} />
<ResumeButton disabled={!selectedSession} />
</div>
)
}
// 会话预览
function SessionPreview({ session }: { session: Session }) {
return (
<div className="session-preview">
<h3>{session.title}</h3>
<p className="session-date">
{formatDate(session.createdAt)}
</p>
<div className="session-summary">
{session.messageCount} 条消息
</div>
<MessageList messages={session.messages.slice(-5)} />
</div>
)
}
// Screen 路由
const screenRoutes = {
'/': ReplScreen,
'/doctor': DoctorScreen,
'/resume': ResumeScreen,
'/settings': SettingsScreen,
'/onboarding': OnboardingScreen
}
// Screen 导航
function navigateTo(path: string) {
window.location.hash = path
}
// 或使用 React Router
<Link to="/doctor">打开 Doctor</Link>
// 自定义 Screen
function CustomScreen() {
return (
<div className="custom-screen">
<h2>My Custom Screen</h2>
{/* 自定义内容 */}
</div>
)
}
// 注册 Screen
registerScreen('custom', CustomScreen)
// Screen 生命周期
interface ScreenLifecycle {
onEnter?: () => void // 进入时
onLeave?: () => void // 离开时
onResume?: () => void // 重新激活时
onPause?: () => void // 暂停时
}
答案:
// 选择依据:
// 1. REPL
// - 主交互界面
// - 大部分时间使用
// 2. Doctor
// - 排错时使用
// - 检查配置问题
// 3. Resume
// - 恢复历史会话
// - 继续之前的工作
答案:
// 调试方法:
// 1. 检查路由
// - URL 是否正确
// - 路由是否注册
// 2. 检查 Props
// - 数据是否传入
// - 状态是否正确
// 3. 检查生命周期
// - onEnter 是否执行
// - 清理是否正确
答案:
// 路由实现:
// 1. 路由配置
const routes = {
'/': ReplScreen,
'/doctor': DoctorScreen,
'/resume': ResumeScreen
}
// 2. 路由匹配
function matchRoute(path: string): Screen {
return routes[path] || DefaultScreen
}
// 3. 导航
function navigate(path: string): void {
window.location.hash = path
}
// 4. 守卫
function canNavigate(from: string, to: string): boolean {
// 检查权限
// 返回 true/false
}
答案:
// 生命周期管理:
interface ScreenLifecycle {
onEnter(): void // 进入时
onLeave(): void // 离开时
onResume(): void // 重新激活
onPause(): void // 暂停时
}
// 实现:
class ScreenManager {
private currentScreen: Screen | null = null
navigate(newScreen: Screen): void {
// 1. 调用当前屏幕的 onLeave
this.currentScreen?.onLeave()
// 2. 切换屏幕
this.currentScreen = newScreen
// 3. 调用新屏幕的 onEnter
this.currentScreen.onEnter()
}
}
| 资源 | 说明 |
| `src/ui/screens/ReplScreen.tsx` | REPL 实现 |
| `src/ui/screens/DoctorScreen.tsx` | Doctor 实现 |
| `src/ui/screens/ResumeScreen.tsx` | Resume 实现 |