• 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.小册总结

这节我们继续完善 playground 的功能。

代码写完后我们希望能通过链接分享出去,别人访问链接就可以看到这段代码。

vue playground 是有这个功能的:

点击分享按钮,链接会复制到剪贴板。

然后在新的浏览器窗口打开,可以看到分享的代码:

我们也来实现下。

大家想一下,我们要分享或者说保存的是哪部分数据呢?

其实就是 context 里的 files。

files 包含所有文件的信息,编辑、编译、预览都是围绕 files 来的。

而 files 是一个对象,我们只需要 JSON.stringify 一下,就变为字符串了。

我们把它放到 url 后面,然后初始化的时候读取出来 JSON.parse 一下,作为 files 的初始数据不就行了?

在 Context.Provider 里设置下 JSON.stringify(files) 到 location.hash

useEffect(() => {
    const hash = JSON.stringify(files);
    window.location.hash = encodeURIComponent(hash);
}, [files]);

当 files 内容变化的时候,会同步修改。

这里还要对字符串 encodeURIComponent 下,把 url 里不支持的字符做下转换:

可以看到,确实会把 files 内容保存到 hash。

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

const getFilesFromUrl = () => {
    let files: Files | undefined;
    try {
        const hash = decodeURIComponent(window.location.hash.slice(1));
        files = JSON.parse(hash);
    } catch (error) {
        console.error(error);
    }
    return files;
};

对 hash decodeURIComponent 一下,然后 JSON.parse 作为 files 的内容。

试下效果:

我在 chrome 里改了一下代码内容,新建了一个 Aaa.tsx 组件。

在别的浏览器打开这个链接试下:

可以看到,App.tsx、Aaa.tsx 组件都是我们改动的内容。

这样分享 url 功能就完成了。

当然,直接把文件内容放到 hash 上不大好,太长了,我们要压缩下。

用 fflate 这个包:

安装下:

npm install --save fflate

在 utils.ts 添加两个方法

import { strFromU8, strToU8, unzlibSync, zlibSync } from "fflate";

export function compress(data: string): string {
    const buffer = strToU8(data);
    const zipped = zlibSync(buffer, { level: 9 });
    const str = strFromU8(zipped, true);
    return btoa(str);
}

export function uncompress(base64: string): string {
    const binary = atob(base64);

    const buffer = strToU8(binary, true);
    const unzipped = unzlibSync(buffer);
    return strFromU8(unzipped);
}

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

compress 方法里,我们先调用 fflate 包的 strToU8 把字符串转为字节数组,然后 zlibSync 压缩,之后 strFromU8 转为字符串。

最后用 btoa 把这个 base64 编码的字符串转为 asc 码。

uncompress 方法正好反过来。

我们调用下试试效果:

useEffect(() => {
    const hash = compress(JSON.stringify(files));
    window.location.hash = hash;
}, [files]);

const getFilesFromUrl = () => {
    let files: Files | undefined;
    try {
        const hash = uncompress(window.location.hash.slice(1));
        files = JSON.parse(hash);
    } catch (error) {
        console.error(error);
    }
    return files;
};

现在,代码内容会压缩后以 asc 码字符串的方式保存在 url 里:

在另一个窗口里打开这个 url:

内容同样能恢复。

这样,代码分享功能就完成了。

在 Header 里加个按钮:

<ShareAltOutlined
    style={{ marginLeft: "10px" }}
    onClick={() => {
        copy(window.location.href);
        message.success("分享链接已复制。");
    }}
/>

这里用到了 copy-to-clipboard 包,安装下:

npm install --save copy-to-clipboard

点击分享按钮,会把 url 复制到剪贴板,可以直接粘贴。

然后我们再来实现下代码下载功能:

我们需要在浏览器里把多个文件打成 zip 包,这需要用到 jszip:

npm install --save jszip

然后触发代码下载,我们用 file-saver:

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

在 utils.ts 加一个 downloadFiles 方法:

export async function downloadFiles(files: Files) {
    const zip = new JSZip();

    Object.keys(files).forEach((name) => {
        zip.file(name, files[name].value);
    });

    const blob = await zip.generateAsync({ type: "blob" });
    saveAs(blob, `code${Math.random().toString().slice(2, 8)}.zip`);
}

然后在 Header 加一个按钮:

<DownloadOutlined
    style={{ marginLeft: "10px" }}
    onClick={async () => {
        await downloadFiles(files);
        message.success("下载完成");
    }}
/>

试一下:

下载成功!

案例代码上传了小册仓库,可以切换到这个 commit 查看:

git reset --hard ea67b94f512f023779077dfedca02e87c6d59b4f

总结

这节我们实现了链接分享、代码下载功能。

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

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

不过最好是做下压缩,我们用 fflate 这个包来对字符串进行压缩,然后用 btoa 转为 asc 码字符串。

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

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

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
58.ReactPlayground项目实战:错误显示、主题切换
Next
60.ReactPlayground项目实战:WebWorker性能优化