• Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • React 通关秘籍

    • 1.关于本小册
    • 2.一网打尽组件常用Hook
    • 3.Hook的闭包陷阱的成因和解决方案
    • 4.React组件如何写TypeScript类型
    • 5.React组件如何调试
    • 6.受控模式VS非受控模式
    • 7.组件实战:迷你Calendar
    • 8.组件实战:Calendar日历组件(上)
    • 9.组件实战:Calendar日历组件(下)
    • 10.快速掌握Storybook
    • 11.React组件如何写单测
    • 12.深入理解Suspense和ErrorBoundary
    • 13.组件实战:Icon图标组件
    • 14.组件实战:Space间距组件
    • 15.React.Children和它的两种替代方案
    • 16.三个简单组件的封装
    • 17.浏览器的5种Observer
    • 18.组件实战:Watermark防删除水印组件
    • 19.手写react-lazyload
    • 20.图解网页的各种距离
    • 21.自定义hook练习
    • 22.自定义hook练习(二)
    • 23.用react-spring做弹簧动画
    • 24.react-spring结合use-gesture手势库实现交互动画
    • 25.用react-transition-group和react-spring做过渡动画
    • 26.快速掌握Tailwind:最流行的原子化CSS框架
    • 27.用CSSModules避免样式冲突
    • 28.CSSInJS:快速掌握styled-components
    • 29.react-spring实现滑入滑出的转场动画
    • 30.组件实战:Message全局提示组件
    • 31.组件实战:Popover气泡卡片组件
    • 32.项目里如何快速定位组件源码
    • 33.一次超爽的React调试体验
    • 34.组件实战:ColorPicker颜色选择器(一)
    • 35.组件实战:ColorPicker颜色选择器(二)
    • 36.组件实战:onBoarding漫游式引导组件
    • 37.组件实战:Upload拖拽上传
    • 38.组件实战:Form表单组件
    • 39.React组件库都是怎么构建的
    • 40.组件库实战:构建esm和cjs产物,发布到npm
    • 41.组件库实战:构建umd产物,通过unpkg访问
    • 42.数据不可变:immutable和immer
    • 43.基于ReactRouter实现keepalive
    • 44.Historyapi和ReactRouter实现原理
    • 45.ReactContext的实现原理和在antd里的应用
    • 46.ReactContext的性能缺点和解决方案
    • 47.手写一个Zustand
    • 48.原子化状态管理库Jotai
    • 49.用react-intl实现国际化
    • 50.国际化资源包如何通过Excel和GoogleSheet分享给产品经理
    • 51.基于react-dnd实现拖拽排序
    • 52.react-dnd实战:拖拽版TodoList
    • 53.ReactPlayground项目实战:需求分析、实现原理
    • 54.ReactPlayground项目实战:布局、代码编辑器
    • 55.ReactPlayground项目实战:多文件切换
    • 56.ReactPlayground项目实战:babel编译、iframe预览
    • 57.ReactPlayground项目实战:文件增删改
    • 58.ReactPlayground项目实战:错误显示、主题切换
    • 59.ReactPlayground项目实战:链接分享、代码下载
    • 60.ReactPlayground项目实战:WebWorker性能优化
    • 61.ReactPlayground项目实战:总结
    • 62.手写MiniReact:思路分析
    • 63.手写MiniReact:代码实现
    • 64.手写MiniReact:和真实React源码的对比
    • 65.React18的并发机制是怎么实现的
    • 66.Ref的实现原理
    • 67.低代码编辑器:核心数据结构、全局store
    • 68.低代码编辑器:拖拽组件到画布、拖拽编辑json
    • 69.低代码编辑器:画布区hover展示高亮框
    • 70.低代码编辑器:画布区click展示编辑框
    • 71.低代码编辑器:组件属性、样式编辑
    • 72.低代码编辑器:预览、大纲
    • 73.低代码编辑器:事件绑定
    • 74.低代码编辑器:动作弹窗
    • 75.低代码编辑器:自定义JS
    • 76.低代码编辑器:组件联动
    • 77.低代码编辑器:拖拽优化、Table组件
    • 78.低代码编辑器:Form组件、store持久化
    • 79.低代码编辑器:项目总结
    • 80.快速掌握ReactFlow画流程图
    • 81.ReactFlow振荡器调音:项目介绍
    • 82.ReactFlow振荡器调音:流程图绘制
    • 83.ReactFlow振荡器调音:合成声音
    • 84.AudioContext实现在线钢琴
    • 85.React服务端渲染:从SSR到hydrate
    • 86.小册总结

我们从 0 到 1 实现了和 vue playground 功能一样的项目:

整体测试一下 react playground 的功能:

我们用 create-vite 创建的项目,进入项目,把开发服务跑起来:

npm run dev

浏览器访问下:

我们可以在左侧编辑器写组件和样式,然后在右边实时预览:

css 和 ts 代码都有提示:

并且我们后面引入的 npm 包也会有类型提示:

但额外引入的 npm 包不能直接用:

需要在 import-map.json 里配置下才行:

文件可以新增、删除、修改:

比如我们新增一个 Aaa.tsx 组件

还有它的样式 Aaa.css

在 App.tsx 里引入后就可以使用:

你可以直接打开这个 url 试试

这个通过 url 分享代码也是 playgrond 的功能。

点击右上角的分享按钮可以复制分享链接:

此外,还可以下载代码到本地:

还有切换亮色、暗色主题:

这些就是 React Playground 的全部功能。

最终实现的效果还不错。

回顾下我们的开发过程:

我们首先写了下布局:

用 allotment 实现的 split-pane,两边可以通过拖动改变大小。

然后集成 @monaco-editor/react 实现的编辑器。

还用 @typescript/ata 包实现了代码改变时自动下载 dts 类型包的功能。

这样,在编辑器里写代码就有 ts 类型提示了。

然后我们实现了多文件的切换:

在 Context 中保存全局数据,比如 files、selectedFileName,还有对应的增删改的方法。

对 Context.Provider 封装了一层来注入初始化数据和方法,提供了 initFiles 的信息。

然后在 FileNameList 里读取 context 里的 files 来渲染文件列表。

点击 tab 的时候切换 selectedFileName,从而切换编辑器的内容。

然后实现了编译以及在 iframe 里预览。

使用 @babel/standalone 做的 tsx 代码的编译,编译过程中需要对 .tsx、.css、.json 等模块的 import 做处理,变成 blob url 的方式。

通过 babel 插件实现了对 import 语句的修改:

css 模块包一层代码加到 head 的 style 标签里,json 包一层代码直接 export,而 tsx 模块直接 babel 编译即可。

对于 react、react-dom/client 这种,用浏览器的 import maps 来引入。

然后通过 iframe 实现了预览:

替换 html 模版里 import maps 和 src 部分的 script 标签后,同样用 blob url 设置为 iframe 的 src 就可以了。

这样就可以预览了:

iframe 的代码如下:

正是替换了 importmap 和 src 部分的 html,并且 css 也被添加到了 head 里的 style 标签下。

然后我们实现了文件的新增、删除、修改:

main.tsx、App.tsx、import-map.json 设置为 readonly,不可编辑和删除。

之后实现了错误的显示,在 iframe 里监听 error 事件,发生错误的时候通过 postMessage 传递给父窗口。

父窗口里监听 message 事件传过来的错误,用 Message 组件显示。

然后实现了主题切换:

主题切换就是在根元素加一个 .light、.dark 的 className,里面声明 css 变量,因为 css 变量可以在子元素里生效,子元素写样式基于这些变量,那切换了 className 也就切换了这些变量的值,从而实现主题切换。

之后实现了通过链接分享代码的功能:

原理就是把 files 信息 JSON.stringify 之后保存到 location.hash。

然后初始化的时候从 location.hash 读取出来 JSON.parse 之后设置到 files。

这个过程中还要做下压缩,用 fflate 这个包来对字符串进行压缩,然后用 btoa 转为 asc 码字符串。

代码下载则是基于 jszip 和 file saver 包实现的。

这样,playground 里写的代码内容就可以通过 url 分享出去,并且可以下载了。

之后做了下性能优化:

用 Performance 分析了页面的 Event Loop,发现有 long task,性能优化的目标就是消除 long task。

分析发现是 babel 编译的逻辑导致的。

我们通过 Web Worker 把 babel 编译的逻辑放到了 worker 线程跑,通过 message 事件和 postMessage 和主线程通信。

拆分后功能正常,再用 Performance 分析,发现耗时逻辑被转移到了 worker 线程,主线程这个 long task 没有了。

这就是我们开发这个 playground 的全过程。

其实这个项目完全可以写到简历里,而且非常好讲故事,比如你开发的组件库,用这个 playground 来实现组件的在线预览。

而且这个项目有挺多技术亮点的:

  • 用 @monaco-editor/react 实现了网页版 typescript 编辑器,并且实现了自动类型导入
  • 通过 @babel/standalone 实现了文件编译,并且写了一个 babel 插件实现了 import 的 source 的修改
  • 通过 blob url 来内联引入其他模块的代码,通过 import maps 来引入 react、react-dom 等第三方包的代码
  • 通过 iframe 实现了预览功能,并且通过 postMessage 和父窗口通信来显示代码运行时的错误
  • 基于 css 变量 + context 实现了主题切换功能
  • 通过 fflate + btoa 实现了文件内容的编码、解码,可以通过链接分享代码
  • 通过 Performance 分析性能问题,并通过 Web Worker 拆分编译逻辑到 worker 线程来进行性能优化,消除了 long lask

而且这些技术点也挺有价值的,通过 playground 项目对这些技术点有很好的掌握之后,在别的地方也能用到。

总之,大家可以把这个项目消化吸收,内化成你自己的东西。

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
60.ReactPlayground项目实战:WebWorker性能优化
Next
62.手写MiniReact:思路分析