打造属于你的前端沙盒 🎉

第一章:项目背景与目标设定

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.tsxindex.tsxutils.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"。
  • 提供 setActiveFileupdateFileContent 方法,方便组件间协作。
  • 自定义 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 实现了代码预览,这里详细讲讲原理和安全性。

技术原理:

  • iframesrcDoc 属性,把用户代码包裹在完整 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 的 Suspenselazy,只在需要时加载编辑器,提升首屏速度。

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 的集成,还掌握了多文件管理、沙盒预览、工程化优化等前端高级技能。最重要的是,项目结构清晰,代码注释详细,后续扩展和维护都很方便。

如果你也想打造属于自己的在线编辑器,不妨参考本项目,动手试试,说不定下一个"爆款工具"就是你写的!💪

相关推荐
AlienZHOU2 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
CSDN官方博客2 小时前
GitHub Copilot 新手极速上手指南
github·copilot
Captaincc5 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师6 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen7 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒7 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
用户5784429228627 小时前
GraphRAG 与 LightRAG
github
峰向AI7 小时前
别再花几万块搭 Agent 了,[微软开源] 3500 行框架让 AI 自己练级
github
前端snow7 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8187 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器