@kne/example-driver
v0.1.41
Published
用于在线展示和编辑React组件
Keywords
Readme
example-driver
描述
用于在线展示和编辑React组件
安装
npm i --save @kne/example-driver概述
项目概述
@kne/example-driver 是一个用于在线展示和编辑 React 组件的工具库,特别适合用于组件库文档、教程演示和技术文档中。它提供了实时代码预览和编辑功能,让用户可以直接在浏览器中查看和修改组件代码,无需搭建完整的开发环境。
主要特性
实时编辑与预览
- LiveCode 模式:提供完整的在线编辑和实时预览能力,内置 Monaco Editor 提供专业的代码编辑体验
- MiniCode 模式:通过二维码引导用户在移动端查看示例,适用于移动端场景
- 语法高亮:使用 Prism 实现代码语法高亮
- Debounce 优化:避免频繁重新渲染,提升编辑体验
性能与加载
- 视口懒加载:组件进入视口时才挂载渲染,离开视口自动卸载并保留占位高度,减少页面初始负载
- 懒编译机制:使用 Babel Standalone 实现浏览器端代码编译,支持 ES2015 和 React 预设,无需后端转换;采用 LRU 缓存和编译队列优化编译性能
- 优先级调度:首次可见时优先编译,后续编辑变更走 debounce 500ms 延迟编译
错误处理与布局
- 错误边界:代码错误不会影响整个页面,提供友好的错误提示
- 响应式布局:支持单列(全宽)和双列模式,自动适应不同屏幕尺寸
- 自定义上下文:支持全局和单个示例级别的自定义上下文组件,方便在不同场景中嵌入示例代码
- 编辑器可配置:Monaco Editor 配置暴露给外部,允许深度定制编辑器行为(如自定义 CDN 路径)
国际化
- 内置中文(zh-CN)和英文(en-US)语言支持,自动适配编辑器加载提示、扫码提示等文案
使用场景
- 组件库文档网站:在线展示组件示例,支持用户直接修改代码查看效果
- 在线教程和培训:让学员在浏览器中即时实践代码
- 技术博客和文档:嵌入可交互的代码示例,提升阅读体验
- React 组件展示平台:通过动态加载和实时编译,让文档中的示例代码真正"活"起来
示例(×)
示例样式
@use '~@kne/responsive-utils/scss' as resp;
.media-demo-box {
padding: 16px;
border-radius: 8px;
background: #e6f4ff;
color: #0958d9;
}
.media-demo-label::after {
content: '桌面端布局';
}
@include resp.mobile-container {
.media-demo-box {
background: #f6ffed;
color: #389e0d;
}
.media-demo-label::after {
content: '移动端布局';
}
}
.viewport-demo-panel {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
min-height: 72px;
height: calc(var(--kne-viewport-height, 100vh) * 0.35);
padding: 12px;
border: 1px dashed #1677ff;
border-radius: 8px;
background: linear-gradient(180deg, #e6f4ff 0%, #bae0ff 100%);
color: #0958d9;
font-size: 13px;
text-align: center;
}
.viewport-demo-panel-label::after {
content: '(桌面:基于 100vh)';
}
@include resp.mobile-container {
.viewport-demo-panel-label::after {
content: '(手机框:基于设备高度 px)';
}
}
.viewport-demo-width-track {
box-sizing: border-box;
width: 100%;
padding: 8px;
border: 1px dashed #52c41a;
border-radius: 8px;
background: #f6ffed;
}
.viewport-demo-width-bar {
box-sizing: border-box;
width: calc(var(--kne-viewport-width, 100vw) * 0.3);
max-width: 100%;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: linear-gradient(90deg, #95de64 0%, #52c41a 100%);
color: #135200;
font-size: 12px;
white-space: nowrap;
}示例代码
- 基础使用
- 展示 ExampleDriver 组件的基本用法,包含简单的计数器示例
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
const {default: ExampleDriver} = _ExampleDriver;
// 示例代码字符串
const code = `
const { Button, Card, Space } = antd;
const { useState } = React;
const Component = () => {
const [count, setCount] = useState(0);
return (
<div style={{ padding: '12px' }}>
<Card>
<h4 style={{ marginBottom: '12px' }}>计数器示例</h4>
<Space>
<Button onClick={() => setCount(count - 1)}>-</Button>
<span style={{ fontSize: '18px' }}>{count}</span>
<Button onClick={() => setCount(count + 1)}>+</Button>
<Button onClick={() => setCount(0)} danger>重置</Button>
</Space>
</Card>
</div>
);
};
render(<Component />);
`;
render(<ExampleDriver list={[{
title: '基本使用', description: 'ExampleDriver 的基础用法,展示简单的计数器示例', code, scope: [{
name: 'antd', packageName: 'antd', component: antd
}]
}]}/>);
- LiveCode 模式
- 实时代码编辑和预览模式,可以直接在浏览器中修改代码并查看效果
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
const {default: ExampleDriver} = _ExampleDriver;
const code = `
const { Button, Input, List, Space, Tag } = antd;
const { useState } = React;
const Component = () => {
const [text, setText] = useState('');
const [items, setItems] = useState(['学习 React', '编写组件']);
const addItem = () => {
if (text.trim()) {
setItems([...items, text.trim()]);
setText('');
}
};
const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index));
};
return (
<div style={{ padding: '12px' }}>
<Space orientation="vertical" style={{ width: '100%' }}>
<Space.Compact style={{ width: '100%' }}>
<Input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="输入事项..."
onPressEnter={addItem}
/>
<Button type="primary" onClick={addItem}>添加</Button>
</Space.Compact>
<List
size="small"
dataSource={items}
renderItem={(item, index) => (
<List.Item
actions={[
<Button type="link" size="small" onClick={() => removeItem(index)}>
删除
</Button>
]}
>
<Tag>{item}</Tag>
</List.Item>
)}
/>
</Space>
</div>
);
};
render(<Component />);
`;
render(<ExampleDriver list={[{
title: 'LiveCode 模式', description: '支持实时代码编辑,可以在编辑器中修改代码并实时预览效果', code, scope: [{
name: 'antd', packageName: 'antd', component: antd
}]
}]}/>);
- MiniCode 模式
- 二维码预览模式,适用于移动端场景,通过二维码引导用户查看示例
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
const {default: ExampleDriver} = _ExampleDriver;
const code = `
const { Form, Input, Button, Space } = antd;
const { useState } = React;
const Component = () => {
const [form] = Form.useForm();
const [data, setData] = useState([]);
const onFinish = (values) => {
setData([...data, values]);
form.resetFields();
};
return (
<div style={{ padding: '12px' }}>
<Form form={form} onFinish={onFinish} layout="vertical">
<Form.Item name="name" label="姓名" rules={[{ required: true }]}>
<Input placeholder="输入姓名" />
</Form.Item>
<Form.Item name="email" label="邮箱" rules={[{ required: true, type: 'email' }]}>
<Input placeholder="输入邮箱" />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">提交</Button>
<Button onClick={() => form.resetFields()}>重置</Button>
</Space>
</Form.Item>
</Form>
{data.length > 0 && (
<div style={{ marginTop: '12px' }}>
<h4>已提交数据:</h4>
{data.map((item, index) => (
<p key={index}>{item.name} - {item.email}</p>
))}
</div>
)}
</div>
);
};
render(<Component />);
`;
render(<ExampleDriver list={[{
title: 'MiniCode 模式',
description: '二维码预览模式,通过 qrcodeUrl 属性添加二维码引导用户查看',
code,
qrcodeUrl: 'https://www.kne-union.top/static-data/mini-core/0.jpg',
scope: [{
name: 'antd', packageName: 'antd', component: antd
}]
}]}/>);
- 自定义上下文
- 使用 contextComponent 属性自定义示例的上下文组件
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
const {default: ExampleDriver} = _ExampleDriver;
const code = `
const { Button, Card, Space, Switch, Tag } = antd;
const { useState } = React;
// 自定义上下文组件
const CustomContext = ({ children }) => {
const [theme, setTheme] = useState('light');
const colors = {
light: { bg: '#fff', color: '#000', border: '#d9d9d9' },
dark: { bg: '#1a1a1a', color: '#fff', border: '#434343' }
};
const style = {
padding: '12px',
background: colors[theme].bg,
color: colors[theme].color,
borderRadius: '4px'
};
return (
<div style={style}>
<Space style={{ marginBottom: '12px' }}>
<Switch
checked={theme === 'dark'}
onChange={(checked) => setTheme(checked ? 'dark' : 'light')}
checkedChildren="🌙"
unCheckedChildren="☀️"
/>
<Tag color={theme === 'dark' ? 'blue' : 'orange'}>
{theme === 'dark' ? '暗色主题' : '亮色主题'}
</Tag>
</Space>
{children}
</div>
);
};
const Component = () => {
return (
<CustomContext>
<p>这是上下文包裹的内容,会根据主题自动改变颜色。</p>
</CustomContext>
);
};
render(<Component />);
`;
render(<ExampleDriver list={[{
title: '自定义上下文',
description: '通过 contextComponent 属性可以为示例代码提供自定义的上下文组件',
code,
contextComponent: ({children}) => (<div style={{padding: '12px', background: '#f5f5f5', borderRadius: '4px'}}>
{children}
</div>),
scope: [{
name: 'antd', packageName: 'antd', component: antd
}]
}]}/>);
- 高度稳定性测试
- 通过 state 控制组件挂载与卸载,验证视口检测和占位高度的一致性
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
const {default: ExampleDriver} = _ExampleDriver;
const {useState} = React;
const {Button, Card, Space, Switch, Tag} = antd;
const code = `
const { Button, Card, Space } = antd;
const { useState } = React;
const Component = () => {
const [count, setCount] = useState(0);
return (
<div style={{ padding: '20px' }}>
<Card title="高度测试组件" style={{ minHeight: '200px' }}>
<Space orientation="vertical" style={{ width: '100%' }}>
<div style={{ fontSize: '32px', textAlign: 'center', padding: '20px 0' }}>
{count}
</div>
<Space>
<Button onClick={() => setCount(count - 1)}>-</Button>
<Button onClick={() => setCount(count + 1)}>+</Button>
<Button onClick={() => setCount(0)} danger>重置</Button>
</Space>
</Space>
</Card>
</div>
);
};
render(<Component />);
`;
const HeightTestExample = () => {
const [mounted, setMounted] = useState(true);
const list = [{
title: '高度稳定性测试',
description: '通过开关控制组件挂载/卸载,观察卸载后占位高度与渲染高度是否一致',
code,
mounted,
scope: [{
name: 'antd', packageName: 'antd', component: antd
}]
}];
return (
<Space orientation="vertical" style={{width: '100%'}} size="large">
<Card size="small">
<Space>
<Switch checked={mounted} onChange={setMounted} checkedChildren="挂载" unCheckedChildren="卸载"/>
<Tag color={mounted ? 'green' : 'red'}>{mounted ? '已挂载' : '已卸载'}</Tag>
<Button size="small" onClick={() => {
setMounted(false);
setTimeout(() => setMounted(true), 100);
}}>快速切换</Button>
<Button size="small" onClick={() => {
for (let i = 0; i < 3; i++) {
setTimeout(() => setMounted(prev => !prev), i * 200);
}
}}>连续切换3次</Button>
<span style={{color: '#999', fontSize: 12}}>
切换后观察页面是否抖动,占位高度是否与渲染高度一致
</span>
</Space>
</Card>
<ExampleDriver isFull list={list}/>
</Space>
);
};
render(<HeightTestExample/>);
- 双列布局(全屏)
- 使用 isFull 属性控制布局,默认双列展示多个示例
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader)
const {default: ExampleDriver} = _ExampleDriver;
const exampleFull = `
const { Alert, Card } = antd;
const Component = () => {
return (
<Card size="small" title="全屏示例">
<Alert message="该项设置了 isFull: true,在外层双列模式下置顶整行显示" type="info" showIcon />
</Card>
);
};
render(<Component />);
`;
const example1 = `
const { Button, Card } = antd;
const Component = () => {
return (
<Card size="small" title="示例 1">
<Button type="primary">按钮 A</Button>
<Button style={{ marginLeft: '8px' }}>按钮 B</Button>
</Card>
);
};
render(<Component />);
`;
const example2 = `
const { Input, Card, Space } = antd;
const { TextArea } = Input;
const Component = () => {
return (
<Card size="small" title="示例 2">
<Space orientation="vertical" style={{ width: '100%' }}>
<Input placeholder="输入内容..." />
<TextArea rows={4} placeholder="多行输入..." />
</Space>
</Card>
);
};
render(<Component />);
`;
const example3 = `
const { Select, Card } = antd;
const { Option } = Select;
const Component = () => {
return (
<Card size="small" title="示例 3">
<Select placeholder="请选择" style={{ width: '100%' }}>
<Option value="1">选项 1</Option>
<Option value="2">选项 2</Option>
</Select>
</Card>
);
};
render(<Component />);
`;
const example4 = `
const { Switch, Card } = antd;
const Component = () => {
return (
<Card size="small" title="示例 4">
<Switch />
</Card>
);
};
render(<Component />);
`;
const scope = [{name: 'antd', packageName: 'antd', component: antd}];
// 双列瀑布流布局:isFull 项置顶整行,其余项按高度分配到两列
render(<ExampleDriver list={[{
title: '按钮示例',
description: '展示按钮组件',
code: example1,
scope
}, {
title: '全屏示例',
description: '设置 isFull: true 后在外层双列模式下置顶整行显示',
code: exampleFull,
scope,
isFull: true
}, {
title: '输入框示例',
description: '展示输入框组件,高度较高以演示瀑布流分配',
code: example2,
scope
}, {
title: '下拉框示例',
description: '展示下拉选择组件',
code: example3,
scope
}, {
title: '开关示例',
description: '展示开关组件',
code: example4,
scope
}]}/>);
- 设备预览(全屏)
- 电脑 / 手机设备切换与 iPhone 边框预览,含超长超宽滚动示例
- _ExampleDriver(@kne/current-lib_example-driver)[import * as _ExampleDriver from "@kne/example-driver"],(@kne/current-lib_example-driver/dist/index.css),antd(antd),remoteLoader(@kne/remote-loader),_ResponsiveUtils(@kne/responsive-utils)[import * as _ResponsiveUtils from "@kne/responsive-utils"]
const {default: ExampleDriver} = _ExampleDriver;
const code = `
const { Button, Card, Space } = antd;
const { useState } = React;
const Component = () => {
const [count, setCount] = useState(0);
return (
<div style={{ padding: '12px' }}>
<Card size="small" title="设备预览示例">
<Space>
<Button onClick={() => setCount(count - 1)}>-</Button>
<span style={{ fontSize: '18px' }}>{count}</span>
<Button onClick={() => setCount(count + 1)}>+</Button>
</Space>
</Card>
</div>
);
};
render(<Component />);
`;
const scrollCode = `
const { Card, Tag, Space } = antd;
const Component = () => {
return (
<div style={{ padding: '12px' }}>
<Space orientation="vertical" style={{ width: '100%' }} size="middle">
<Space wrap>
<Tag color="blue">纵向超长</Tag>
<Tag color="green">横向超宽</Tag>
</Space>
<div style={{
minWidth: '800px',
padding: '16px',
background: 'linear-gradient(90deg, #e6f4ff, #f6ffed)',
borderRadius: '8px',
whiteSpace: 'nowrap',
}}>
横向超宽区域(800px)—— 切换到手机预览后左右滑动查看完整内容
</div>
{Array.from({ length: 20 }, (_, index) => (
<Card key={index} size="small" title={'区块 ' + (index + 1)}>
纵向超长内容,共 20 个区块,用于验证手机框内纵向滚动,以及 header / footer 固定效果。
</Card>
))}
</Space>
</div>
);
};
render(<Component />);
`;
const mediaQueryCode = `
const { Card, Tag, Space } = antd;
const Component = () => {
const isMobile = useIsMobile();
return (
<div style={{ padding: '12px' }}>
<Card size="small" title="媒体查询示例">
<Space orientation="vertical" style={{ width: '100%' }} size="middle">
<div className="media-demo-box">
<div className="media-demo-label">当前为</div>
<div>切换电脑 / 手机预览,观察背景色与文案变化</div>
</div>
<Tag color={isMobile ? 'success' : 'processing'}>
useIsMobile:{isMobile ? '移动端' : '桌面端'}
</Tag>
<Tag color="default">样式见 doc/style.scss,按响应式容器宽度生效</Tag>
</Space>
</Card>
</div>
);
};
render(<Component />);
`;
const viewportCode = `
const { Card, Descriptions, Space, Tag, Typography } = antd;
const {
VIEWPORT_WIDTH_VAR,
VIEWPORT_HEIGHT_VAR,
useBreakpoint,
useResponsiveContext
} = _ResponsiveUtils;
const { useEffect, useRef, useState } = React;
const readRunnerViewportVars = (node) => {
const runner = node && node.closest('.example-driver-runner');
if (!runner) {
return { width: '-', height: '-' };
}
const style = getComputedStyle(runner);
return {
width: style.getPropertyValue(VIEWPORT_WIDTH_VAR).trim() || '-',
height: style.getPropertyValue(VIEWPORT_HEIGHT_VAR).trim() || '-'
};
};
const Component = () => {
const isMobile = useIsMobile();
const breakpoints = useBreakpoint();
const { mode, containerWidth } = useResponsiveContext();
const rootRef = useRef(null);
const [vars, setVars] = useState({ width: '-', height: '-' });
useEffect(() => {
setVars(readRunnerViewportVars(rootRef.current));
}, [mode, containerWidth, isMobile]);
const activeKeys = Object.keys(breakpoints).filter((key) => key !== 'isMobile' && breakpoints[key]);
return (
<div ref={rootRef} style={{ padding: '12px' }}>
<Card size="small" title="视口 API 演示">
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<Descriptions column={1} size="small" bordered>
<Descriptions.Item label="实际vw">{window.innerWidth}px</Descriptions.Item>
<Descriptions.Item label="实际vh">{window.innerHeight}px</Descriptions.Item>
<Descriptions.Item label="Provider mode">{mode}</Descriptions.Item>
<Descriptions.Item label="containerWidth">
{typeof containerWidth === 'number' ? containerWidth + 'px' : '—'}
</Descriptions.Item>
<Descriptions.Item label={VIEWPORT_WIDTH_VAR}>{vars.width}</Descriptions.Item>
<Descriptions.Item label={VIEWPORT_HEIGHT_VAR}>{vars.height}</Descriptions.Item>
<Descriptions.Item label="useIsMobile">
<Tag color={isMobile ? 'success' : 'processing'}>
{isMobile ? '移动端' : '桌面端'}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="useBreakpoint">
{activeKeys.join(', ') || 'xs'}
</Descriptions.Item>
</Descriptions>
<Typography.Text type="secondary">
切换到「手机」并更换机型,变量与断点应随设备宽高变化;桌面(电脑档)模拟视口宽 80vw、高 100vh。
</Typography.Text>
<div className="viewport-demo-panel">
<span className="viewport-demo-panel-label">高度 = var(--kne-viewport-height) × 35%</span>
</div>
<div className="viewport-demo-width-track">
<div className="viewport-demo-width-bar">
</div>
宽度 = var(--kne-viewport-width) × 30%
</div>
</Space>
</Card>
</div>
);
};
render(<Component />);
`;
const scope = [
{name: 'antd', packageName: 'antd', component: antd},
{name: '_ResponsiveUtils', packageName: '@kne/responsive-utils', component: _ResponsiveUtils}
];
render(<>
<ExampleDriver list={[
{
title: '默认设备切换',
description: '默认开启电脑 / 手机切换,手机模式下可切换 iPhone Pro Max / Pro / SE',
code,
scope
},
{
title: '超长超宽滚动',
description: '内容同时超出纵向与横向视口,切换到手机模式可验证 SimpleBar 滚动与固定 header / footer',
code: scrollCode,
scope
},
{
title: '视口 API',
description: '演示 @kne/responsive-utils 视口 CSS 变量与 useIsMobile / useBreakpoint 在手机预览框内是否按设备尺寸生效',
code: viewportCode,
scope
},
{
title: '关闭设备切换',
description: '设置 devicePreview: false 隐藏设备切换',
code,
scope,
devicePreview: false
}
]}/>
<ExampleDriver isFull list={[
{
title: '媒体查询响应',
description: '示例使用 doc/style.scss 中的 mobile-container 与 useIsMobile,切换到手机预览后应显示移动端布局样式',
code: mediaQueryCode,
scope: scope,
isFull: true
}
]}/>
</>);
API
ExampleDriver
用于展示和编辑 React 组件示例的主组件,支持 LiveCode 和 MiniCode 两种展示模式。
属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | list | array | 是 | - | 示例列表数组,每个元素代表一个示例 | | isFull | boolean | 否 | false | 是否全宽显示,true 时单列显示,false 时双列显示。当 list 仅有一项时自动为 true | | contextComponent | React Component | 否 | - | 自定义上下文组件,用于包裹渲染的代码内容 | | className | string | 否 | - | 自定义 CSS 类名 | | ...props | object | 否 | - | 其他 HTML div 元素的属性 |
list 数组项属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | code | string | 是 | - | 示例代码字符串 | | scope | array | 是 | - | 作用域数组,包含组件和包信息 | | title | string | 是 | - | 示例标题 | | description | string | 是 | - | 示例描述,支持 HTML 格式 | | qrcodeUrl | string | 否 | - | 二维码图片 URL,传入此项则使用 MiniCode 模式 | | contextComponent | React Component | 否 | - | 单个示例的自定义上下文组件,优先级高于 ExampleDriver 的 contextComponent | | mounted | boolean | 否 | - | 控制组件是否挂载。传入时由该值决定;不传时由视口检测决定是否挂载 | | useInView | boolean | 否 | true | 是否启用视口检测懒加载,仅在 mounted 未传入时生效。设为 false 则组件始终挂载 | | isFull | boolean | 否 | false | 是否全宽显示。仅当外层 isFull 为 false 时生效,设为 true 时该项置顶并独占一行 |
scope 数组项属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | component | any | 否 | - | 要暴露给示例代码的组件,不传时仅生成 import 语句(如 CSS 导入) | | name | string | 否 | - | 组件名称,在代码中使用。component 为真值且 name 为非空字符串时才注入到示例作用域 | | packageName | string | 否 | - | 包名,用于生成导入语句 | | importStatement | string | 否 | - | 自定义导入语句,提供后不再自动生成。优先级最高 |
LiveCode
提供实时代码编辑和预览功能的子组件(通过 ExampleDriver 自动使用)。
属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | code | string | 否 | '' | 要展示的代码 | | scope | array | 否 | [] | 作用域数组 | | title | string | 是 | - | 标题 | | description | string | 是 | - | 描述,支持 HTML | | contextComponent | React Component | 否 | - | 上下文组件,包裹渲染的示例内容 | | mounted | boolean | 否 | - | 控制组件是否挂载。传入时由该值决定;不传时由视口检测决定 | | useInView | boolean | 否 | true | 是否启用视口检测懒加载,仅在 mounted 未传入时生效 |
MiniCode
显示二维码预览的只读模式子组件(通过 ExampleDriver 自动使用)。
属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | code | string | 是 | - | 示例代码 | | qrcodeUrl | string | 是 | - | 二维码图片地址 | | scope | array | 是 | - | 作用域数组 | | title | string | 是 | - | 标题 | | description | string | 是 | - | 描述 |
CodePanel
代码编辑面板子组件,用于显示和编辑代码。
属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| code | string | 是 | - | 代码字符串 |
| scope | array | 是 | - | 作用域数组,用于生成 import 语句 |
| error | Error | null | 否 | - | 编译错误对象 |
| editable | boolean | 是 | - | 代码是否可编辑 |
| onChange | function | 否 | - | 代码变更回调 (newCode: string) => void |
DescriptionBar
示例描述栏子组件,显示标题、描述和代码面板切换按钮。
属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | title | string | 是 | - | 示例标题 | | description | string | 是 | - | 示例描述,支持 HTML | | codeOpen | boolean | 是 | - | 代码面板是否打开 | | onToggle | function | 是 | - | 切换代码面板的回调 |
HighlightCode
代码高亮展示子组件,使用 Prism 实现语法高亮。
属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | code | string | 是 | - | 要高亮显示的代码字符串 |
ErrorComponent
错误展示子组件,用于显示编译或运行时错误。
属性说明
| 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | error | string | Error | 否 | - | 错误信息,字符串直接显示,Error 对象显示 .message |
Hooks
useInView
视口检测 Hook,用于判断元素是否进入视口,实现懒加载。
| 参数 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | ref | React.RefObject | 是 | - | 要监听的 DOM 容器 ref | | options | object | 否 | - | 配置选项 | | options.disabled | boolean | 否 | false | 是否禁用视口检测 |
| 返回值 | 类型 | 说明 | |--------|------|------| | shouldRender | boolean | 元素是否在视口内 | | heightRef | React.MutableRefObject<number> | 记录元素最后一次渲染高度的 ref |
内部使用全局共享的 IntersectionObserver 实例,rootMargin 为 200px 预加载,离开视口且 intersectionRatio 为 0 时卸载并保留占位高度。SSR 环境或浏览器不支持 IntersectionObserver 时直接返回 shouldRender 为 true。
useLazyCompile
懒编译 Hook,使用 Babel Standalone 在浏览器端编译代码,支持缓存和优先级调度。
| 参数 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | code | string | 是 | - | 要编译的代码字符串 | | shouldCompile | boolean | 是 | - | 是否应该编译,通常关联 useInView 的 shouldRender |
| 返回值 | 类型 | 说明 | |--------|------|------| | compiledCode | string | null | 编译后的代码,未编译时为 null | | error | Error | null | 编译错误,无错误时为 null |
内部采用 LRU 缓存(上限 200 条),编译队列最大并发为 1。首次可见时作为优先任务立即编译,后续编辑变更走 debounce 500ms。非优先任务使用 requestIdleCallback 延迟执行。
useReactRoot
React Root 管理 Hook,使用 react-dom/client 的 createRoot API 管理组件挂载/卸载。
| 参数 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | containerRef | React.RefObject | 是 | - | 渲染容器 DOM ref | | shouldRender | boolean | 是 | - | 是否应该渲染 | | renderJsx | ReactNode | null | 是 | - | 要渲染的 React 元素 | | heightRef | React.MutableRefObject<number> | 是 | - | 用于记录/恢复高度的 ref |
卸载时将 runner 替换为 placeholder 并保留上次高度,使用 ResizeObserver 监控高度变化。组件卸载时自动清理。
config
Monaco Editor 的配置方法,用于自定义编辑器加载行为。
| 方法名 | 参数 | 返回值 | 说明 | |--------|------|--------|------| | config | options: { paths?: Record<string, string>, [key: string]: any } | void | 配置 Monaco Editor 的加载选项,最常用场景是通过 paths 自定义 Monaco 的 CDN 路径 |
国际化
内置中文(zh-CN)和英文(en-US)语言支持,默认语言为 zh-CN。
| Key | zh-CN | en-US | |-----|-------|-------| | CodePanel.loading | 正在加载代码编辑器... | Loading code editor... | | MiniCode.example | 示例 | Example | | MiniCode.scanQrcode | 请扫描二维码查看示例程序 | Please scan the QR code to view the example |
