项目全解析
组件架构与 UI 系统设计
解析 React 组件树、Monaco 编辑器集成、CSS 变量主题系统
- 理解 React 组件的分层设计
- 掌握 Monaco Editor 的 React 集成方式
- 理解 CSS 变量主题系统
- 学会设计可复用的 UI 组件
组件分层架构
Python
# 朋友好学组件树
print("""
Layout(布局层)
├── TopNav 顶部导航栏(毛玻璃效果)
├── CourseSidebar 左侧课程目录(桌面端)
└── MobileNav 底部导航栏(手机端)
Page(页面层)
├── HomePage 落地页
├── Dashboard 学习仪表盘
├── CoursePage 课程学习页
│ ├── ContentBlock 内容渲染器(递归)
│ │ ├── CodeBlock 代码块 → CodeEditor
│ │ ├── FillBlank 填空题
│ │ ├── ChoiceQuestion 选择题
│ │ └── Callout 提示框
│ └── ChallengePanel 挑战任务面板
└── LabPage 代码实验室
Feature(功能层)
├── CodeEditor Monaco 编辑器 + Pyodide 执行
├── CodingKeyboard 手机端编程键盘
└── AIAssistant 豆包悬浮助手
UI(基础层)
├── Button 按钮(6 种变体 × 4 种尺寸)
├── Card 卡片
└── Badge 徽章
""")🐍组件分层原则
基础 UI 组件不知道任何业务逻辑;功能组件封装可复用功能;页面组件组合功能组件。依赖方向只能从上到下:Page → Feature → UI,UI 组件永远不依赖 Page。
CodeEditor 组件核心结构
JavaScript
// components/editor/CodeEditor.tsx
export function CodeEditor({
initialCode, height = 300, autoSize = false,
maxAutoHeight = 800, readOnly = false,
}: CodeEditorProps) {
const [code, setCode] = useState(initialCode);
const [isFocused, setIsFocused] = useState(false);
const [editorHeight, setEditorHeight] = useState(autoSize ? 80 : height);
const { isLoading, result, run } = usePythonExecutor();
const handleMount = (editor, monaco) => {
editorRef.current = editor;
if (autoSize) {
const updateHeight = () => {
const h = editor.getContentHeight();
setEditorHeight(Math.min(maxAutoHeight, Math.max(80, h)));
editor.layout({ width: editor.getLayoutInfo().width, height: h });
};
updateHeight();
editor.onDidContentSizeChange(updateHeight);
editor.onDidFocusEditorText(() => setIsFocused(true));
editor.onDidBlurEditorText(() => setIsFocused(false));
}
};
return (
<MonacoEditor
height="100%"
language="python"
value={code}
onMount={handleMount}
options={{
minimap: { enabled: false },
wordWrap: "on",
padding: { top: 4, bottom: 4 },
renderLineHighlight: isFocused ? "all" : "none",
scrollbar: {
vertical: autoSize ? "hidden" : "auto",
alwaysConsumeMouseWheel: false,
},
}}
/>
);
}
/*
* 关键设计:
* 1. autoSize:onDidContentSizeChange 比手动计算更准确
* 2. renderLineHighlight:未聚焦时不高亮当前行(减少视觉噪音)
* 3. alwaysConsumeMouseWheel: false:滚轮不劫持页面滚动
* 4. next/dynamic 懒加载:Monaco 依赖浏览器 API,不能 SSR
*/CSS 变量主题系统
示例
/* globals.css */
:root, [data-theme="dark"] {
--background: #000000;
--card: #1c1c1e;
--foreground: #f5f5f7;
--primary: #4f46e5;
--success: #10b981;
--danger: #ef4444;
--border: rgba(255,255,255,0.08);
--code-bg: #0d1117;
}
[data-theme="light"] {
--background: #f2f2f7;
--card: #ffffff;
--foreground: #1d1d1f;
--border: rgba(0,0,0,0.08);
--code-bg: #f6f8fa;
}
/* 毛玻璃效果 */
.glass-nav {
background-color: rgba(10,10,12,0.55);
backdrop-filter: blur(24px) saturate(200%);
}
/*
* 主题切换原理:
* 1. next-themes 在 <html> 上设置 data-theme
* 2. CSS 变量根据属性选择器自动切换
* 3. 所有 var(--xxx) 自动更新,零 JS 开销
*/Button 组件设计
JavaScript
const variants = {
primary: "bg-[var(--primary)] text-white hover:bg-[var(--primary-hover)]",
secondary: "bg-[var(--secondary)]",
outline: "border border-[var(--border-strong)]",
ghost: "hover:bg-[var(--muted)]",
destructive: "bg-[var(--danger)] text-white",
success: "bg-[var(--success)] text-white",
};
const sizes = {
sm: "h-8 px-3 text-sm",
md: "h-9 px-4 text-sm",
lg: "h-11 px-6 text-base",
icon: "h-9 w-9",
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = "primary", size = "md", loading, children, ...props }, ref) => (
<button ref={ref} className={cn("btn-base", variants[variant], sizes[size])}
disabled={loading || props.disabled} {...props}>
{loading && <Spinner />}
{children}
</button>
)
);💡cn() 工具函数
cn() = clsx() + twMerge()。clsx 处理条件类名,twMerge 解决 Tailwind 类名冲突(如同时有 px-3 和 px-4,后者覆盖前者)。
选择题
Monaco Editor 使用 next/dynamic 动态加载的原因是什么?
选择题
让代码编辑器随内容行数自动变高,最合理的实现是?
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
路由级代码分割与懒加载、图片懒加载并提供合适尺寸、输入用防抖、滚动用节流、超长列表用虚拟滚动;memo/useMemo/useCallback 不要无脑加,先用性能工具定位真实瓶颈。优化顺序永远是「测量 → 找到最贵的部分 → 针对性改 → 再测」。
挑战任务
计算代码编辑器的自适应高度
简单+50 XP
编辑器要随代码行数自动变高,高度就是「行数 × 每行高度 + 上下内边距」。实现函数 editor_height,参数是 line_count,line_height 默认 20,padding 默认 16。
计算代码编辑器的自适应高度
Python 0%
1 个测试用例