打造属于你的前端沙盒 🎉

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

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

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

相关推荐
用户47949283569153 小时前
🤫 你不知道的 JavaScript:`"👦🏻".length` 竟然不是 1?
前端·javascript·面试
掘金一周3 小时前
凌晨零点,一个TODO,差点把我们整个部门抬走 | 掘金一周 9.11
前端·人工智能·后端
用户8174413427483 小时前
kubernetes核心概念 Service
前端
东北南西3 小时前
Web Worker 从原理到实战 —— 把耗时工作搬到后台线程,避免页面卡顿
前端·javascript
Zz_waiting.3 小时前
案例开发 - 日程管理 - 第六期
前端·javascript·vue.js·路由·router
袁煦丞3 小时前
企业微信开发者的‘跨网穿梭门’:cpolar内网穿透实验室第499个成功挑战
前端·程序员·远程工作
Simon_He3 小时前
vue-markdown-renderer:比 vercel streamdown 更低 CPU、更多节点支持、真正的流式渲染体验
前端·vue.js·markdown
小桥风满袖3 小时前
极简三分钟ES6 - 模块化
前端·javascript
练习时长一年3 小时前
自定义事件发布器
java·前端·数据库