第一章:项目背景与目标设定
1.1 为什么要做一个 React 在线编辑器?
在前端开发圈,在线编辑器简直是"神器"------不管是新手练手、面试刷题,还是老手分享 Demo,大家都离不开它。市面上的 CodeSandbox、StackBlitz、JSFiddle 等虽然功能强大,但总有些"不够定制"的地方。于是,我决定撸一个属于自己的 React 在线编辑器,既能满足个性化需求,也能深入学习前端工程化和沙盒技术。
目标很明确:
- 支持 React 代码实时编辑与预览
- 支持多文件管理
- 体验流畅,界面美观
- 代码结构清晰,易于扩展
- 性能优秀,加载快不卡顿
1.2 项目特色一览
本项目采用 Vite + React + TypeScript,前端主流技术栈,工程化体验一流。支持多文件编辑、实时预览、代码高亮、自动保存等功能。项目结构合理,代码注释详细,适合学习和二次开发。
1.3 页面效果展示

第二章:技术选型与环境搭建
2.1 技术选型
- React:组件化开发,生态丰富,适合构建复杂交互界面
- Vite:极速开发体验,热更新快,配置简单,适合现代前端项目。
- TypeScript:类型安全,代码更健壮,IDE 支持更友好。
- Monaco Editor:微软出品的在线代码编辑器,VS Code 同款体验。
- Sass:样式预处理,支持嵌套和变量,提升 CSS 维护性。
2.2 环境搭建
2.2.1 初始化项目
首先,使用 Vite 创建 React + TypeScript 项目:
bash
npm create vite@latest react在线编辑器 -- --template react-ts
cd react在线编辑器
npm install
2.2.2 项目结构
项目结构如下(部分):
css
├── index.html
├── package.json
├── src/
│ ├── App.tsx
│ ├── App.scss
│ ├── main.tsx
│ ├── components/
│ │ ├── CodeEditor/
│ │ ├── Header/
│ │ └── Preview/
│ ├── ReactPlayground/
│ │ ├── PlaygroundContext.tsx
│ │ ├── files.ts
│ │ ├── index.tsx
│ │ └── utils.ts
2.2.3 依赖安装
除了 Vite 默认依赖,还需要安装 Monaco Editor:
bash
npm install monaco-editor
第三章:项目结构与核心文件详解
3.1 项目结构一览
先来一张项目结构图,方便大家"摸清家底":
css
├── index.html
├── package.json
├── src/
│ ├── App.tsx
│ ├── App.scss
│ ├── main.tsx
│ ├── components/
│ │ ├── CodeEditor/
│ │ ├── Header/
│ │ └── Preview/
│ ├── ReactPlayground/
│ │ ├── PlaygroundContext.tsx
│ │ ├── files.ts
│ │ ├── index.tsx
│ │ └── utils.ts
是不是有点像"前端小别墅",每个房间都有自己的功能?🏡
3.2 index.html ------ 项目入口
这个文件就像"房子的门",所有人都得从这里进来:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>React在线开发沙盒</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
思路讲解:
<div id="root"></div>
是 React 应用的挂载点,所有页面内容都在这里"生长"。<script type="module" src="/src/main.tsx"></script>
通过 Vite 加载入口文件,支持现代 ES Module。
3.3 src/main.tsx ------ React 应用入口
这个文件就像"总开关",一按就点亮整个应用:
tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './App.scss';
// 挂载根组件
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
思路讲解:
- 使用 React 18 的新 API
createRoot
,性能更优。 - 引入全局样式
App.scss
,让页面颜值在线。
3.4 src/App.tsx ------ 应用主组件
这里是"指挥中心",负责调度各个功能模块:
tsx
import React from 'react';
import Header from './components/Header';
import ReactPlayground from './ReactPlayground';
import './App.scss';
/**
* 应用主组件
* 负责页面布局和核心功能模块的组合
*/
const App: React.FC = () => {
return (
<div className="app-container">
<Header />
<ReactPlayground />
</div>
);
};
export default App;
思路讲解:
- 顶部是导航栏 Header,下面是核心编辑区 ReactPlayground。
- 结构清晰,方便后续扩展,比如加侧边栏、弹窗等。
3.5 src/App.scss ------ 全局样式
颜值很重要,样式不能少:
scss
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
background: #f5f7fa;
}
思路讲解:
- 使用 Flex 布局,让页面自适应屏幕高度。
- 背景色选用淡灰色,既清爽又护眼。
第四章:编辑器核心功能实现
4.1 顶部导航(Header)
4.1.1 组件结构
src/components/Header/index.tsx
tsx
import React from 'react';
import './Header.scss';
/**
* 顶部导航栏组件
* 展示项目标题、操作按钮等
*/
const Header: React.FC = () => {
return (
<header className="header">
<h1>React在线开发沙盒 🚀</h1>
<div className="actions">
{/* 这里可以放置保存、导入、导出等操作按钮 */}
</div>
</header>
);
};
export default Header;
思路讲解:
- 标题和操作区分离,方便后续加功能,比如"保存"、"导入"、"导出"。
- 标题加个火箭 🚀,寓意项目飞速成长!
4.1.2 样式设计
src/components/Header/Header.scss
scss
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
background: #282c34;
color: #fff;
h1 {
font-size: 1.5rem;
margin: 0;
}
.actions {
button {
margin-left: 8px;
background: #61dafb;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
&:hover {
background: #21a1f3;
}
}
}
}
思路讲解:
- Flex 布局让标题和操作按钮左右分布。
- 主题色采用 React 官方配色,提升辨识度。
4.2 代码编辑区(CodeEditor)
4.2.1 组件结构
这里要隆重介绍一个"明星依赖"------Monaco Editor!🎉
Monaco Editor 是微软出品的在线代码编辑器,VS Code 的亲兄弟,支持高亮、自动补全、语法检查等功能。用它做在线编辑器,体验直接拉满!
src/components/CodeEditor/index.tsx
tsx
import React, { useRef, useEffect } from 'react';
// 引入 Monaco Editor,支持代码高亮和智能提示
import * as monaco from 'monaco-editor';
import './CodeEditor.scss';
interface CodeEditorProps {
value: string; // 当前代码内容
language: string; // 代码语言(如 'javascript', 'typescript', 'jsx')
onChange: (value: string) => void; // 代码变化回调
}
/**
* 代码编辑器组件
* 封装 Monaco Editor,支持高亮、自动补全等
*/
const CodeEditor: React.FC<CodeEditorProps> = ({ value, language, onChange }) => {
const editorRef = useRef<HTMLDivElement>(null);
const monacoInstance = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
useEffect(() => {
if (editorRef.current) {
// 创建 Monaco 编辑器实例
monacoInstance.current = monaco.editor.create(editorRef.current, {
value,
language,
theme: 'vs-dark',
automaticLayout: true,
fontSize: 16,
});
// 监听代码变化
monacoInstance.current.onDidChangeModelContent(() => {
const newValue = monacoInstance.current!.getValue();
onChange(newValue);
});
}
// 卸载时销毁编辑器实例,防止内存泄漏
return () => {
monacoInstance.current?.dispose();
};
}, []);
// 外部 value 变化时同步到编辑器
useEffect(() => {
if (monacoInstance.current && monacoInstance.current.getValue() !== value) {
monacoInstance.current.setValue(value);
}
}, [value]);
return <div className="code-editor" ref={editorRef} style={{ height: '100%', width: '100%' }} />;
};
export default CodeEditor;
思路讲解:
- 用
useRef
持有编辑器 DOM 节点和实例,方便操作。 useEffect
初始化编辑器,并监听内容变化,实时同步代码。- 支持多语言高亮,后续可以扩展更多语言。
- 卸载时记得销毁实例,防止"内存大爆炸"💣。
4.2.2 样式设计
src/components/CodeEditor/CodeEditor.scss
scss
.code-editor {
height: 100%;
width: 100%;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
思路讲解:
- 保证编辑器区域美观,圆角和阴影提升高级感。
4.3 预览区(Preview)
代码写完,当然要"马上预览"!这里用到了 iframe 技术,让你的代码在沙盒里安全运行,互不干扰。
src/components/Preview/index.tsx
tsx
import React from 'react';
import './Preview.scss';
interface PreviewProps {
code: string; // 需要预览的代码内容
}
/**
* 预览区组件
* 使用 iframe 隔离运行用户代码,保证安全性
*/
const Preview: React.FC<PreviewProps> = ({ code }) => {
// 构建完整 HTML 内容
const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>预览区</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
try {
${code}
} catch (e) {
document.body.innerHTML = '<pre style="color: red;">' + e.toString() + '</pre>';
}
</script>
</body>
</html>
`;
return (
<iframe
className="preview-iframe"
srcDoc={html}
title="代码预览"
sandbox="allow-scripts"
style={{ width: '100%', height: '100%', border: 'none', background: '#fff' }}
/>
);
};
export default Preview;
思路讲解:
- 用
iframe
隔离运行用户代码,防止"熊孩子"代码影响主页面。 sandbox="allow-scripts"
只允许脚本运行,安全性更高。- 错误捕获后显示红色提示,用户体验更友好。
4.3.1 样式设计
src/components/Preview/Preview.scss
scss
.preview-iframe {
width: 100%;
height: 100%;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
background: #fff;
}
第五章:文件管理与上下文设计------让你的编辑器"多才多艺" 🎭
5.1 为什么要做文件管理?
如果只能编辑一个文件,那就像只能吃一种口味的薯片,久了肯定腻!前端项目通常由多个文件组成,比如 App.tsx
、index.tsx
、utils.ts
等。支持多文件编辑,才能让你的在线编辑器"像真项目一样"灵活。
5.2 文件结构与数据模型
文件管理的核心是"文件列表"和"当前激活文件"。我们用一个对象数组来描述所有文件,每个文件有名字、内容、类型等属性。
src/ReactPlayground/files.ts
ts
// 文件数据模型定义
export interface FileData {
name: string; // 文件名,如 App.tsx
content: string; // 文件内容
language: string; // 语言类型,如 'typescript', 'javascript', 'jsx'
}
// 默认文件列表
export const defaultFiles: FileData[] = [
{
name: 'App.tsx',
content: `import React from 'react';
export default function App() {
return <h2>Hello React 在线编辑器!👋</h2>;
}
`,
language: 'typescript',
},
{
name: 'utils.ts',
content: `export function add(a: number, b: number) {
return a + b;
}
`,
language: 'typescript',
},
];
思路讲解:
- 每个文件都是一个对象,方便后续扩展,比如加"只读"、"隐藏"等属性。
- 默认文件列表让用户一进来就有内容可玩,不会"空空如也"。
5.3 文件上下文管理------用 Context 统一状态
React 的 Context 就像"中央调度室",可以让多个组件共享状态,比如当前文件列表、激活文件、代码内容变化等。
src/ReactPlayground/PlaygroundContext.tsx
tsx
import React, { createContext, useContext, useState } from 'react';
import { FileData, defaultFiles } from './files';
// 定义上下文类型
interface PlaygroundContextType {
files: FileData[]; // 所有文件
activeFile: FileData; // 当前激活文件
setActiveFile: (name: string) => void; // 切换激活文件
updateFileContent: (name: string, content: string) => void; // 更新文件内容
}
// 创建上下文
const PlaygroundContext = createContext<PlaygroundContextType | undefined>(undefined);
/**
* PlaygroundProvider 组件
* 提供文件管理和状态共享能力
*/
export const PlaygroundProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [files, setFiles] = useState<FileData[]>(defaultFiles);
const [activeFileName, setActiveFileName] = useState<string>(defaultFiles[0].name);
// 获取当前激活文件
const activeFile = files.find(f => f.name === activeFileName)!;
// 切换激活文件
const setActiveFile = (name: string) => setActiveFileName(name);
// 更新文件内容
const updateFileContent = (name: string, content: string) => {
setFiles(files =>
files.map(f => (f.name === name ? { ...f, content } : f))
);
};
return (
<PlaygroundContext.Provider
value={{ files, activeFile, setActiveFile, updateFileContent }}
>
{children}
</PlaygroundContext.Provider>
);
};
// 自定义 Hook,方便使用上下文
export function usePlayground() {
const ctx = useContext(PlaygroundContext);
if (!ctx) throw new Error('usePlayground 必须在 PlaygroundProvider 内使用');
return ctx;
}
思路讲解:
- 用 Context 统一管理文件列表和当前激活文件,避免"层层传递 props"。
- 提供
setActiveFile
和updateFileContent
方法,方便组件间协作。 - 自定义 Hook
usePlayground
,让代码更优雅。
5.4 文件切换与编辑------"左手切文件,右手写代码" 👐
在编辑器界面,通常左侧是文件列表,右侧是代码编辑区。我们可以这样实现:
src/ReactPlayground/index.tsx
tsx
import React from 'react';
import { PlaygroundProvider, usePlayground } from './PlaygroundContext';
import CodeEditor from '../components/CodeEditor';
import Preview from '../components/Preview';
import './index.scss';
/**
* ReactPlayground 组件
* 负责文件列表、代码编辑和预览的组合
*/
const PlaygroundMain: React.FC = () => {
const { files, activeFile, setActiveFile, updateFileContent } = usePlayground();
return (
<div className="playground-container">
<aside className="file-list">
{files.map(file => (
<div
key={file.name}
className={`file-item${file.name === activeFile.name ? ' active' : ''}`}
onClick={() => setActiveFile(file.name)}
>
{file.name}
</div>
))}
</aside>
<section className="editor-preview">
<CodeEditor
value={activeFile.content}
language={activeFile.language}
onChange={content => updateFileContent(activeFile.name, content)}
/>
<Preview code={activeFile.content} />
</section>
</div>
);
};
// 用 Provider 包裹主内容,提供上下文能力
const ReactPlayground: React.FC = () => (
<PlaygroundProvider>
<PlaygroundMain />
</PlaygroundProvider>
);
export default ReactPlayground;
思路讲解:
- 左侧文件列表支持点击切换,当前激活文件高亮显示。
- 右侧代码编辑区和预览区并列,实时同步代码内容。
- 用 Provider 包裹主内容,保证所有子组件都能访问上下文。
5.4.1 样式设计
src/ReactPlayground/index.scss
scss
.playground-container {
display: flex;
height: calc(100vh - 64px); // 减去 Header 高度
}
.file-list {
width: 180px;
background: #fff;
border-right: 1px solid #eee;
padding: 16px 0;
.file-item {
padding: 8px 24px;
cursor: pointer;
color: #333;
&.active {
background: #e3f2fd;
color: #1976d2;
font-weight: bold;
}
&:hover {
background: #f5f5f5;
}
}
}
.editor-preview {
flex: 1;
display: flex;
flex-direction: column;
padding: 16px;
gap: 16px;
}
思路讲解:
- 文件列表宽度固定,编辑区自适应。
- 激活文件高亮,提升用户体验。
- 编辑区和预览区间距适当,布局舒适。
第六章:代码高亮与语法支持------"让代码闪闪发光" ✨
6.1 Monaco Editor 的高亮魔法
前面已经用 Monaco Editor 实现了代码编辑区,这里补充一下它的高亮和智能提示功能。
依赖介绍:
- Monaco Editor 支持多种语言高亮和智能补全,比如 TypeScript、JavaScript、CSS、HTML 等。
- 只需设置
language
属性即可自动切换高亮模式。
代码片段:
tsx
<CodeEditor
value={activeFile.content}
language={activeFile.language} // 这里自动切换高亮
onChange={content => updateFileContent(activeFile.name, content)}
/>
思路讲解:
- 用户切换不同文件时,编辑器自动切换高亮模式,体验"丝滑"。
- 后续可以扩展更多语言,比如 JSON、Markdown,只需加一行配置。
第七章:实时预览与沙盒隔离------"写完马上看,安全不翻车" 🛡️
7.1 预览区实现原理
前面已经用 iframe 实现了代码预览,这里详细讲讲原理和安全性。
技术原理:
- 用
iframe
的srcDoc
属性,把用户代码包裹在完整 HTML 结构里。 - 用
sandbox="allow-scripts"
属性,限制 iframe 只能运行脚本,不能访问主页面资源。 - 捕获运行错误,友好提示用户。
代码片段:
tsx
const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>预览区</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
try {
${code}
} catch (e) {
document.body.innerHTML = '<pre style="color: red;">' + e.toString() + '</pre>';
}
</script>
</body>
</html>
`;
<iframe
className="preview-iframe"
srcDoc={html}
title="代码预览"
sandbox="allow-scripts"
style={{ width: '100%', height: '100%', border: 'none', background: '#fff' }}
/>
思路讲解:
- 用户写的代码不会影响主页面,哪怕写了
window.location.href = 'https://baidu.com'
也不会跳转。 - 错误捕获后用红色提示,防止"白屏找不到原因"。
第八章:项目工程化与性能优化------"让编辑器飞起来" 🚀
8.1 工程化配置
项目采用 Vite 作为构建工具,优点是"快、简单、热更新爽"。配置文件 vite.config.ts
可以定制端口、代理、别名等。
代码片段:
ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000, // 自定义端口
},
resolve: {
alias: {
'@': '/src', // 路径别名,方便引用
},
},
});
思路讲解:
- 用别名
@
代替/src
,引用组件更方便。 - 端口可自定义,避免和其他项目冲突。
8.2 性能优化技巧
8.2.1 编辑器懒加载
Monaco Editor 体积较大,可以用懒加载优化首屏速度:
tsx
const MonacoEditor = React.lazy(() => import('monaco-editor'));
<Suspense fallback={<div>加载编辑器中...</div>}>
<MonacoEditor ... />
</Suspense>
思路讲解:
用 React 的 Suspense
和 lazy
,只在需要时加载编辑器,提升首屏速度。
8.2.2 代码分割与按需加载
Vite 默认支持代码分割,页面只加载当前需要的模块,减少无用资源。
8.2.3 样式优化
用 Sass 变量和嵌套,减少重复代码,提升样式维护性。
第九章:踩坑记录与解决方案------"那些年掉过的坑" 🕳️
9.1 Monaco Editor 加载失败
问题: 有时 Monaco Editor 加载不出来,页面一片空白。
解决:
- 检查依赖是否安装完整。
- 确认样式文件是否正确引入。
- 用
useEffect
销毁实例,防止内存泄漏。
9.2 iframe 预览报错
问题: 用户代码有语法错误,预览区直接白屏。
解决:
- 用 try-catch 包裹用户代码,捕获错误并显示红色提示。
9.3 文件切换不同步
问题: 切换文件后,编辑器内容没刷新。
解决:
- 用
useEffect
监听value
变化,及时同步到编辑器。
第十章:结语------成长与收获 🎓
这次从零到一实现 React 在线编辑器,收获满满!不仅学会了 Monaco Editor 的集成,还掌握了多文件管理、沙盒预览、工程化优化等前端高级技能。最重要的是,项目结构清晰,代码注释详细,后续扩展和维护都很方便。
如果你也想打造属于自己的在线编辑器,不妨参考本项目,动手试试,说不定下一个"爆款工具"就是你写的!💪