浅谈 web worker 和 react component

好久不见

前言

相信 web worker 对大家来说也不是个新东西了,笔者今天要讲的 web worker 和 react component 之间的事情其实也不是什么新东西了。

但是相信很多人其实并没有很了解其中的概念,因为相关的文章太少了,在掘金社区中搜索目前也只有 @LucasHC 大佬讲过我所了解的那些东西。

很多人认为这个方案不可行因为很多测试结果表示简单的页面上 worker 和 主线程 之间的通信时间的花费还不如直接在主线程渲染 react 组件,但实际上很多的业务是足够复杂的:

各位打工人可以想想自家的 shit 山是不是有很难优化的运行时瓶颈),web worker 或许能起到你意想不到的优化效果(画🫓 ):)

本文只是一篇发散思考的文章,探索 react-worker-component 的原理和其背后的思想,希望也能为诸君带来一些启发。

免责声明:本文代码为了方便理解仅作示例用,不保证可运行 🤣

当 worker 经常被这样使用

示例

想一想,如果让你在 react 中使用 worker 优化性能,你是不是大概会这样做:

javascript 复制代码
const worker = new Worker(workerJsUrl);

function NoteComonent({sourceData}) {
    const [props, setProps] = useState(null)
    useEffect(() => {
        const updater = (e) => {
           setProps(e.data);
        };
        
        worker.postMessage(sourceData)
        worker.addEventListener('message', updater)
        
        return () => { worker.removeEventListener('message', updater) }
    }, [sourceData]);
    
    return props ? <Detail {...props} /> : <p>loading...</p>
}

实际上,这个过程中我们并没有结合 react,只是在做"使用 worker 处理数据"这件事情

问题

这个写法有问题吗?--- 完全没有任何问题

但是实际的项目中并不总是给你一个书写全新的业务逻辑的任务,很多时候你需要优化那些可怕的老代码,这时候还使用上面的方式就要面临一个巨大的难题:

你需要将老代码的数据处理逻辑全部都抽离到 worker js 中,同时原有的渲染组件也可能要改造以适配对应的模式

如果上面的难题对你来说并不是件难事,那么恭喜你遇到了一个没那么复杂 / 维护很好 的项目,但改造总是有成本、风险的问题的,那么还有什么方式呢?请看下文

React worker component

react-worker-component 是开源牛人 dai-shi (zustand、jotai、waku 等著名库作者)受 RSC 启发所做的一次探索性尝试,github 地址:github.com/dai-shi/rea...

原理介绍,和上述所说的使用方式不同,他采用的方式如下:

  1. React component 逻辑完全运行在 worker 里面并且最终也是生成了 JSX 数据

  2. JSX 数据通过 序列化****和反序列化 传递到主线程

  3. 由于反序列化后的 JSX 和正常的 JSX 没什么两样,可以自然被 React Vdom 体系收纳运行

如何序列化 JSX

上图的其他部分大家都能理解,但是可能会对 JSX 的序列化和反序列化感到无比的迷惑,JSX 是 react 生成的特定对象,主线程和 worker 又不能变量共享,怎么可能做到呢?

恭喜你!和你想的一样,能序列化说明肯定是以纯数据的传输的,那么 JSX 到底是什么样的数据呢,不妨在控制台打印看看:

其实组成一个组件的关键因素就两个:type 和 props

scss 复制代码
type(props) = JSX.Element

所以 dai-shi 使用的方法大概是这样的(笔者为了方便理解的极简版实现)

序列化:主要逻辑是处理了 jsx element 类型的情况下的 type 和 props

javascript 复制代码
// serialize 序列化所有 jsx 中可能包含的类型,本代码仅有 obj 和 element,原库还实现了 array、null 和基本类型等等
function serialize(x) {
    if(x.$$typeof === Symbol.for('react.element')) {
        const e = {
          // 序列化 props,采用递归因为 props 中可能有 children 而并只是纯数据
          props: serialize(x.props),
          // 这里假设所有的 type 都是 html 元素 (原库还实现了内嵌 react 组件)
          type: x.type,
        };
        // 细节:使用 e 而不是 element 主要是减少序列化的 size
        return { e };
    }
    if (Object.getPrototypeOf(x) === Object.prototype) {
        const o = {};
        Object.entries(x).forEach(([key, val]) => {
          // val 可能是 element 也可能是别的什么
          o[key] = serialize(val);
        });
        return { o };
      }
}

反序列化:主要逻辑是将序列化出来的 e 重新运行 react 的 createElement 方法生成 JSX.Element

ini 复制代码
export const deserialize = (x) => {
  if ("e" in x) {
    // 关键点:使用 createElement 重新生成 JSX.Element
    const ele = createElement(
      x.e.type,
      deserialize(x.e.props),
    );
    return ele;
  }
  if ("o" in x) {
    const obj = {};
    Object.entries(x.o).forEach(([key, val]) => {
      obj[key] = deserialize(val);
    });
    return obj;
  }
};

看到这里相信你豁然开朗了,原来也不过如此。但是我们要意识到,这样做之后理想情况下原有的 react 组件无需做任何的变更便可以直接使用 worker 去加速渲染!

别急着评论,看看这段

当然你可能会吐槽上面的方式也有很多问题,如 只能渲染纯展示组件没有任何交互能力、主线程再运行一遍 createElement 纯属脱裤子放 P 等等

这里我想谈谈我的理解:

首先,这个库是 dai-shi 受到 react server component 启发所做,不了解 RSC 的建议好好看看 react 官方帖子中的视频,你肯定能豁然开朗:

react.dev/blog/2020/1...

RSC 在 2020 年的时候已经在 facebook 的真实环境灰度使用过,并且直到今天为止 react team 依然对于 RSC 抱着积极、活跃的态度,所以这个方向肯定是可行、有使用场景的。

  1. RSC 也必然遇到序列化和反序列化带来的一系列问题,同时必然会提供框架层面的解决方案

  2. 可交互问题可能没有想象那么严重,RSC demo 中也示例了交互组件对应的构建和协同形式,也有望在框架层面磨平开发体验

  3. createElement 主线程虽然又执行了一次,但是很多复杂的项目优化到这个级别已经有了巨大的提升了(况且这块损耗随着 react team 的优化也有望缩减到更少)

react-worker-component 确实不是一个已经 ready 的方案,但是其思路完全就是 RSC 的衍生,值得我们借鉴学习。

很久以后,或许 react 不仅是 react component 构建出来的,而是由 RSC、RWC、RNC...等共同协作构建而成

后记

笔者认为这将是一个应用的全新构建模式,或许会彻底改变前端应用的体验和应用开发的分工,更不必说可能带来很多衍生技术,笔者也会继续学习、尝试和分享相关的技术,欢迎同好交流、关注。

相关推荐
etsuyou1 分钟前
Koa学习
服务器·前端·学习
Easonmax17 分钟前
【CSS3】css开篇基础(1)
前端·css
大鱼前端35 分钟前
未来前端发展方向:深度探索与技术前瞻
前端
昨天;明天。今天。40 分钟前
案例-博客页面简单实现
前端·javascript·css
天上掉下来个程小白41 分钟前
请求响应-08.响应-案例
java·服务器·前端·springboot
周太密1 小时前
使用 Vue 3 和 Element Plus 构建动态酒店日历组件
前端
时清云2 小时前
【算法】合并两个有序链表
前端·算法·面试
小爱丨同学2 小时前
宏队列和微队列
前端·javascript
持久的棒棒君2 小时前
ElementUI 2.x 输入框回车后在调用接口进行远程搜索功能
前端·javascript·elementui
2401_857297912 小时前
秋招内推2025-招联金融
java·前端·算法·金融·求职招聘