关于 docs-sandpack
docs-sandpack 是一项用于编写 React Sandpack 示例的文档技能,面向文档作者。它涵盖文件结构、代码风格、行标记和示例依赖配置。
主要功能
- 01单文件与多文件示例模式
- 02文件命名及可见性规范
- 03代码行高亮与标注
- 04React 示例风格要求
- 05依赖与 package.json 指引
适用场景
- 01编写文档中的 React 示例
- 02组织跨多个文件的示例
- 03标记代码行与预期错误
- 04检查 Sandpack 示例风格
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ reactjs/ react.dev --skill docs-sandpack安装后在 Claude Code 中输入/docs-sandpack确认已加载。
详细介绍
Sandpack 模式
快速入门模板
大多数示例都是单文件的。复制此模板并进行修改:
<Sandpack>
` ` `js
import { useState } from 'react';
export default function Example() {
const [value, setValue] = useState(0);
return (
<button onClick={() => setValue(value + 1)}>
Clicked {value} times
</button>
);
}
` ` `
</Sandpack>
文件命名
| 模式 | 用途 |
|---|---|
```js | 主文件(无前缀) |
```js src/FileName.js | 辅助文件 |
```js src/File.js active | 活动文件(参考页面) |
```js src/data.js hidden | 隐藏文件 |
```css | CSS 样式 |
```json package.json | 外部依赖 |
重要: 主文件必须包含 export default。
行高亮
```js {2-4}
function Example() {
// Lines 2-4
// will be
// highlighted
return null;
}
代码引用(带编号的标注)
```js [[1, 4, "age"], [2, 4, "setAge"]]
// Creates numbered markers pointing to "age" and "setAge" on line 4
预期错误(有意写错的示例)
```js {expectedErrors: {'react-compiler': [7]}}
// Line 7 shows as expected error
多文件示例
<Sandpack>
```js src/App.js
import Gallery from './Gallery.js';
export default function App() {
return <Gallery />;
}
export default function Gallery() {
return <h1>Gallery</h1>;
}
h1 { color: purple; }
```
External Dependencies
<Sandpack>
```js
import { useImmer } from 'use-immer';
// ...
{
"dependencies": {
"immer": "1.7.3",
"use-immer": "0.5.1",
"react": "latest",
"react-dom": "latest",
"react-scripts": "latest"
}
}
```
Code Style in Sandpack (Required)
Sandpack examples are held to strict code style standards:
- Function declarations for components (not arrows)
efor event parameters- Single quotes in JSX
constunless reassignment needed- Spaces in destructuring:
({ props })not({props}) - Two-line createRoot: separate declaration and render call
- Multiline if statements: always use braces
Don't Create Hydration Mismatches
Sandpack examples must produce the same output on server and client:
// 🚫 This will cause hydration warnings
export default function App() {
const isClient = typeof window !== 'undefined';
return <div>{isClient ? 'Client' : 'Server'}</div>;
}
使用 Ref 保存不参与渲染的状态
// 🚫 Don't trigger re-renders for non-visual state
const [mounted, setMounted] = useState(false);
useEffect(() => { setMounted(true); }, []);
// ✅ Use ref instead
const mounted = useRef(false);
useEffect(() => { mounted.current = true; }, []);
forwardRef 和 memo 模式
forwardRef - 使用具名函数
// ✅ Named function for DevTools display name
const MyInput = forwardRef(function MyInput(props, ref) {
return <input {...props} ref={ref} />;
});
// 🚫 Anonymous loses name
const MyInput = forwardRef((props, ref) => { ... });
memo - 使用具名函数
// ✅ Preserves component name
const Greeting = memo(function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
});
行长度
- 正文:约 80 个字符
- 代码:约 60–70 个字符
- 拆分过长的行,避免水平滚动
反模式
| 模式 | 问题 | 修正方法 |
|---|---|---|
const Comp = () => {} | 不符合惯例 | function Comp() {} |
onClick={(event) => ...} | 与全局变量冲突 | onClick={(e) => ...} |
用 useState 保存不参与渲染的值 | 会重新渲染 | 使用 useRef |
渲染期间读取 window | 水合不匹配 | 在 useEffect 中检查 |
| 不带花括号的单行 if | 更难调试 | 使用带花括号的多行写法 |
链式调用 createRoot().render() | 不够清晰 | 拆成两条语句 |
//... 后没有空格 | 格式不一致 | 使用带空格的 // ... |
| 制表符 | 格式不一致 | 使用 2 个空格 |
ReactDOM.render | 已弃用 | 使用 createRoot |
| 虚构的包名 | 容易造成混淆 | 使用 './your-storage-layer' |
PropsWithChildren | 已过时 | 使用 children?: ReactNode |
列表中缺少 key | 会产生警告 | 始终包含 key |
其他代码质量规则
列表项始终包含 Key
// ✅ Correct
{items.map(item => <li key={item.id}>{item.name}</li>)}
// 🚫 Wrong - missing key
{items.map(item => <li>{item.name}</li>)}
使用真实的导入路径
// ✅ Correct - descriptive path
import { fetchData } from './your-data-layer';
// 🚫 Wrong - looks like a real npm package
import { fetchData } from 'cool-data-lib';
Console.log 标签
// ✅ Correct - labeled for clarity
console.log('User:', user);
console.log('Component Stack:', errorInfo.componentStack);
// 🚫 Wrong - unlabeled
console.log(user);
延迟时间应合理
// ✅ Correct - 1-1.5 seconds
setTimeout(() => setLoading(false), 1000);
// 🚫 Wrong - too long, feels sluggish
setTimeout(() => setLoading(false), 3000);
更新行高亮
修改带有行高亮({2-4})的示例代码时,务必更新高亮行号,使其与新代码一致。行号错误会导致渲染崩溃。
文件名约定
- 组件文件的文件名首字母大写:使用
Gallery.js,而不是gallery.js - 初次说明文件位于
src/后,只用文件名指代文件:使用Gallery.js,而不是src/Gallery.js
代码命名约定
组件: PascalCase
Profile、Avatar、TodoList、PackingList
状态变量: 使用解构模式
const [count, setCount] = useState(0)- 布尔值:
[isOnline, setIsOnline]、[isPacked, setIsPacked] - 状态字符串:
'typing'、'submitting'、'success'、'error'
事件处理函数:
handleClick、handleSubmit、handleAddTask
回调属性:
onClick、onChange、onAddTask、onSelect
自定义 Hooks:
useOnlineStatus、useChatRoom、useFormInput
Reducer 操作:
- 使用过去时:
'added'、'changed'、'deleted' - 复合名称使用蛇形命名法:
'changed_selection'、'sent_message'
更新函数: 使用单个字母
setCount(n => n + 1)
教学用代码标记
错误代码与正确代码:
// 🔴 Avoid: redundant state and unnecessary Effect
// ✅ Good: calculated during rendering
用于讲解生命周期的 Console.log:
console.log('✅ Connecting...');
console.log('❌ Disconnected.');
服务端/客户端标注
// Server Component
async function Notes() {
const notes = await db.notes.getAll();
}
// Client Component
"use client"
export default function Expandable({children}) {
const [expanded, setExpanded] = useState(false);
}
Bundle 大小注释
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
Sandpack 示例指南
Package.json 规则
以下情况应包含 package.json:
- 使用外部 npm 包(immer、remarkable、leaflet、toastify-js 等)
- 演示实验性或 canary 版 React 功能
- 需要指定 React 版本(
react: beta、react: 19.0.0-rc-*)
以下情况不应包含 package.json:
- 示例仅使用 React 内置功能
- 无需外部依赖
- 讲解基础 Hooks、状态或组件
始终将 package.json 标记为隐藏文件:
```json package.json hidden
{
"dependencies": {
"react": "latest",
"react-dom": "latest",
"react-scripts": "latest",
"immer": "1.7.3"
}
}
**Version conventions:**
- Use `"latest"` for stable features
- Use exact versions only when compatibility requires it
- Include minimal dependencies (just what the example needs)
### Hidden File Patterns
**Always hide these file types:**
| File Type | Reason |
|-----------|--------|
| `package.json` | Configuration not the teaching point |
| `sandbox.config.json` | Sandbox setup is boilerplate |
| `public/index.html` | HTML structure not the focus |
| `src/data.js` | When it contains sample/mock data |
| `src/api.js` | When showing API usage, not implementation |
| `src/styles.css` | When styling is not the lesson |
| `src/router.js` | Supporting infrastructure |
| `src/actions.js` | Server action implementation details |
**Rationale:**
- Reduces cognitive load
- Keeps focus on the primary concept
- Creates cleaner, more focused examples
**Example:**
```mdx
```js src/data.js hidden
export const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
];
### Active File Patterns
**Mark as active when:**
- File contains the primary teaching concept
- Learner should focus on this code first
- Component demonstrates the hook/pattern being taught
**Effect of the `active` marker:**
- Sets initial editor tab focus when Sandpack loads
- Signals "this is what you should study"
- Works with hidden files to create focused examples
**Most common active file:** `src/index.js` or `src/App.js`
**Example:**
```mdx
```js src/App.js active
// This file will be focused when example loads
export default function App() {
// ...
}
### File Structure Guidelines
| Scenario | Structure | Reason |
|----------|-----------|--------|
| Basic hook usage | Single file | Simple, focused |
| Teaching imports | 2-3 files | Shows modularity |
| Context patterns | 4-5 files | Realistic structure |
| Complex state | 3+ files | Separation of concerns |
**Single File Examples (70% of cases):**
- Use for simple concepts
- 50-200 lines typical
- Best for: Counter, text inputs, basic hooks
**Multi-File Examples (30% of cases):**
- Use when teaching modularity/imports
- Use for context patterns (4-5 files)
- Use when component is reused
**File Naming:**
- Main component: `App.js` (capitalized)
- Component files: `Gallery.js`, `Button.js` (capitalized)
- Data files: `data.js` (lowercase)
- Utility files: `utils.js` (lowercase)
- Context files: `TasksContext.js` (named after what they provide)
### Code Size Limits
- Single file: **<200 lines**
- Multi-file total: **150-300 lines**
- Main component: **100-150 lines**
- Supporting files: **20-40 lines each**
### CSS Guidelines
**Always:**
- Include minimal CSS for demo interactivity
- Use semantic class names (`.panel`, `.button-primary`, `.panel-dark`)
- Support light/dark themes when showing UI concepts
- Keep CSS visible (never hidden)
**Size Guidelines:**
- Minimal (5-10 lines): Basic button styling, spacing
- Medium (15-30 lines): Panel styling, form layouts
- Complex (40+ lines): Only for layout-focused examples
评论
支持 Markdown