实现 React 的 Playground(4)

345 阅读8分钟

# 实现 React 的 Playground(1)

# 实现 React 的 Playground(2)

# 实现 React 的 Playground(3)

vue Playground

前面三章已经实现了一个react的playGround,主要功能:

1.我们利用@monaco-editor/react做编辑器,

2.利用iframe做预览器,

3.利用@typescript/ata实现编辑器里面ts的检测

4.利用contextApi做文件数据中心

5.利用@babel/standalone来实现浏览器编译react和ts

最终实现的效果如图所示:

image.png

这一章节主要对整页面做优化处理:

一. 处理错误

当前如果页面错误,预览区就会出现白屏,在F12里面你会看到具体的报错信息。如图

image.png

最好的办法就是把这个错误显示到预览区上去

vuePlayGround的错误处理

image.png

我们可以模仿他,也可以将错误直接显示在预览器上,我喜欢将错误直接显示在预览器上,简单粗暴。我们尝试看看:

1.1 拿到编译错误

拿到错误以后,我们希望错误能够安装它原有的格式展示在页面上,而不是被转化成html文本来展示,所以用到pre标签

*pre标签**是‌HTML**中的一个标签,用于定义预格式化的文本。 * 在pre元素中的文本会保留空白字符(如空格和换行符),并且文本会按照源代码的格式显示,而不会被浏览器解释为HTML元素或样式。‌

pre标签的好处:

image.png

所以我们写的错误组件是这样的:

image.png

这个页面很简单,就是一个div上展示错误,在右上角显示一个关闭按钮就好了。面板的样式需要根据错误和警告发生变化,警告是黄色,错误是红色。

1.2把错误显示到预览器上

image.png

new Error().stack!.toString()实际上是创建了一个新的Error对象,然后访问它的stack属性,并将其转换为字符串。这个表达式中的!操作符是用来断言stack属性不为null或undefined。等于:

let stack = (new Error().stack || '').toString();

1.3错误哪里来?

在iframe的html上我们需要监听error,然后将监听到的错误用通过 postMessage 传递给父窗口。

image.png

在预览器里面我们接收下消息

  const [error, setError] = useState('');

  const handleMessage = (msg: MessageData) => {
    const { type, message } = msg.data;
    if (type === 'ERROR') {
      setError(message);
    }
  };

  useEffect(() => {
    window.addEventListener('message', handleMessage);
    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, []);

image.png

测试

image.png

二.切换主题

实现效果如下: image.png

1.在context里面设置主题

主题切换需要的数据应该存储在最顶层的context里面,这样所有的组件都能快速的拿到数据 image.png

主题切换的原理:声明一些全局的 css 变量,写样式的时候用这些变量。切换主题时切换不同的全局变量值即可。

2.在父组件样式里面定义变量

在index.css下面上声明样式:它申明了一个light的样式,一个是dark的样式。

image.png

3.父组件接入样式

然后在index.tsx下面接入

image.png

这样只要在父组件下面定义的样式变量,在子组件里面都可以用var获取到。

4.子组件使用样式变量

比如在Header组件里面

image.png

其他地方的样式也是类同处理!

5.在Header组件里面添加主题切换按钮

antd的icon已经被提取到了一个专门的包里面:npm i @ant-design/icons -S

image.png

image.png

测试

image.png

6.编辑区添加样式-文件名

image.png

7.编辑器:

image.png

8.测试效果

image.png

三.链接分享

点击分享按钮,链接会复制到剪贴板。然后在新的浏览器窗口打开,可以看到分享的代码。

其实说白了,其实咱们要分享是地址和context数据。因为整个页面,不管是编辑器也好,预览器也好,还是fileList,他们都是围绕context在做事。

那怎么将地址和context数据一起发给对方呢?我们选用的是location.hash,就是把context存放到hash里面去。

1 在provide里面给url加数据

我们要清楚的是:当前的地址里面就应该把files的数据带上,而不是等分享的时候再去处理

image.png 测试

image.png

2用url在其他浏览器上打开

那把这个 url 分享出去之后,初始化的时候用 hash 中的 files 就好了:

image.png 值的注意的是,在files里面的value是字符串,即使在编辑器是js文件,实际上他就是字符串,所有我们直接可以用JSON.parse()处理。

测试

image.png 这就算是成功了。

3压缩files文件

如果文件太多,咱们直接放到hash里面很不好,所以我们希望能够把files压缩以后,在传到hash里面。我们选用的工具是:fflate

fflate 是一个快速、轻量级且纯JavaScript实现的压缩库,用于处理gzip、zlib和Deflate格式的数据压缩与解压缩。

它专注于提供高性能的压缩算法实现,特别适合于浏览器环境Node.js环境中使用,且不依赖任何外部库。fflate的优势是体积小,速度快,所以他是很多前端项目中压缩数据的首选库。

该库主要特点包括:

  • 无依赖:fflate是自包含的,不需要额外加载其他库。
  • 高性能:相比其他JavaScript压缩库,fflate在压缩和解压缩速度上表现更优。
  • 体积小:压缩后的库体积很小,有助于减少项目的总体大小。
  • 跨平台:同时支持浏览器和Node.js环境。
  • 流式处理:支持流式读写,可以处理大文件而不用担心内存溢出问题。
  • 完整支持:实现了Deflate、Zlib和Gzip的压缩与解压缩功能。
  • 使用fflate,你可以很容易地对字符串、ArrayBuffer或TypedArray进行压缩和解压缩操作。

例如,以下是一个简单的使用示例:

// 引入fflate
import { gzip, ungzip } from 'fflate';

// 压缩数据
const originalData = 'This is some example data to compress.';
const compressed = gzipSync(originalData);

// 解压缩数据
const decompressed = ungzip(compressed);
console.log(decompressed); // 输出原始数据

安装

npm install --save fflate

写两个工具函数,压缩和解压

image.png 这里的 atob、btoa 是二进制的 ASC 码和 base64 的字符串的转换:

在useEffect里面压缩 image.png

在getFilesFromUrl里面解压

image.png

image.png

4 查看效果:

image.png

image.png

所以对发送url来说,里面的内容压缩是非常有必要的,你只需要用专门的工具压缩,再解压即可。

5 header里面添加按钮

此时的复制了就需要一个剪切板的工具:copy-to-clipboard

npm install --save copy-to-clipboard

image.png

image.png

四.代码下载

编辑器里面的文件还是下载下来,本地使用,是不是很开心?

首先下载代码使用的工具是file-saver,下载下来需要压缩打包,使用jszip

回忆一下上面我们对url里面hash压缩用的是fflate,现在对文件压缩用的jszip

我们看看他们的对比:

image.png

所以此时我们选择了jszip,再看看file-saver他是个纯前端导出工具,如果你想把前端的数据导成一个excel,那就可以是使用file-saver,:参考文章,其实你可以用这个工具把前端的啥都可以导出来。字符串,文件参考

image.png

1.安装

npm install --save jszip
npm install --save file-saver 
npm install --save-dev @types/file-saver

2.在utils/index.ts里面封装一个下载文件的函数

我们需要用jszipfiles里面的每一个文件都打包,形成一个zip以后,用file-saver下载

image.png

3.接入按钮

在header的上面提那家一个下载按钮,然后实现点击按钮,直接下载文件。

image.png

4.测试

image.png

五.性能优化

1. 调试说明

性能优化第一步就是查看他的指标,我们先看看页面指标

image.png

是不是下一调,我们看到performance才有38分,就说明代码需要优化了,具体哪里需要优化进入performance看看,就是要看他有没有长任务。

在这篇文章里面就明确的告诉你了,performance怎么使用,有不懂的,可以参考:# 从 EventLoop 到 Proformance ,再到 Web Work

灰色就代表宏任务 task

橙色的是浏览器内部的 JS

蓝色的是 html 的 parse

紫色是样式的 reflow、repaint

绿色的部分就是渲染

其余的颜色都是用户 JS 的执行了

所以在performance的时候,你时刻关注的是除上面颜色以外的东西

需要优化的地方如下:

image.png

性能优化的目标就是消除这种 long task。

image.png

image.png

image.png

2.处理compile.ts

长任务的解决办法就是Web Worker

image.png

image.png

vite里面的使用方式:

import MyWorker from './worker?worker' 
const worker = new MyWorker()

简单使用:在compiler里面发送一条消息,主线程接受下

image.png

测试如下

image.png

其实他们之间的通信就是 postMessage 和监听 message 事件,一边发送消息,另一边监听消息。如果主线程把files的内容发送给conpile.ts编译,那边只要监听这个消息就好了。

在预览器发送消息

image.png

conpile.ts里面处理编译工作

image.png

优化结果:

image.png

再看看performance work线程里面

image.png 主线程里面 image.png

我们的compile的问题就没有。

其他还需要编译,就请使用相同的方式即可。