You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React 18中如何实现每次setState后触发重新渲染?

React 18中Wasm日志实时渲染问题解决方案

问题核心原因

React 18的自动批处理会合并同步触发的状态更新,而Wasm是同步阻塞主线程执行的——在Wasm运行期间,所有setOut调用都会被积压,直到Wasm执行完成后才会一次性触发渲染,导致日志批量显示。你尝试的flushSync无效,是因为主线程被Wasm完全占用,根本没有间隙处理React的渲染任务。

正确解决方案

1. 用微任务触发React渲染(替代setTimeout Hack)

不需要异步函数,而是在每次setOut后加入一个空的微任务,让React在微任务周期中处理渲染,这是比setTimeout更规范、更轻量的方式:

cell.stdOut = (ln: string) => {
  setOut((prevOut: string) => prevOut === "" ? ln : `${prevOut}\n${ln}`);
  queueMicrotask(() => {});
};

微任务会在当前同步代码块执行完毕后立即执行,能让React及时处理状态更新,实现逐行渲染的效果,同时不会引入不必要的延迟。

2. 将Wasm移至Web Worker运行(最优解)

如果Wasm的执行不需要直接操作DOM,最彻底的解决方式是把Wasm放到Web Worker中运行:

  • 主线程负责UI渲染,Worker负责执行Wasm并通过postMessage发送日志
  • 主线程监听Worker的消息,收到日志时更新状态,此时不会被Wasm阻塞,自然能实时渲染

示例代码:

// 主线程代码
import { useState } from 'react';

export default function WasmRunner() {
  const [out, setOut] = useState("");
  const wasmWorker = new Worker('./wasm-worker.js');

  wasmWorker.onmessage = (e) => {
    if (e.data.type === 'stdout') {
      setOut(prev => prev ? `${prev}\n${e.data.content}` : e.data.content);
    }
  };

  const runWasm = () => {
    wasmWorker.postMessage({ action: 'execute' });
  };

  return (
    <div>
      <button onClick={runWasm}>执行Wasm</button>
      <pre>{out}</pre>
    </div>
  );
}
// wasm-worker.js
// 这里加载并执行Wasm,将stdOut重定向为postMessage
import wasmModule from './your-wasm-module.wasm';

async function initWasm() {
  const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmModule));
  // 重定向stdOut
  instance.exports.stdOut = (ln) => {
    self.postMessage({ type: 'stdout', content: ln });
  };
}

initWasm();

self.onmessage = (e) => {
  if (e.data.action === 'execute') {
    // 调用Wasm的执行函数
    instance.exports.run();
  }
};

3. 备选:使用useRef结合forceUpdate(不推荐)

如果上述方式都无法实现,可以用useRef保存日志内容,手动触发更新,但这种方式会跳过React的状态优化,可能引发性能问题:

import { useState, useRef } from 'react';

export default function WasmRunner() {
  const [, setForceUpdate] = useState(0);
  const outRef = useRef("");

  const forceUpdate = () => setForceUpdate(prev => prev + 1);

  cell.stdOut = (ln: string) => {
    outRef.current = outRef.current === "" ? ln : `${outRef.current}\n${ln}`;
    forceUpdate();
  };

  return (
    <div>
      <button onClick={runWasm}>执行Wasm</button>
      <pre>{outRef.current}</pre>
    </div>
  );
}

关于模拟与实际Wasm的表现

你用循环同步调用stdOut的模拟方式,和实际Wasm运行时的表现完全一致——因为Wasm是同步阻塞执行的,所有stdOut调用都会在同一个同步任务中完成,React会批量处理这些状态更新。只有当Wasm的输出被拆分为不同的任务(比如通过Worker、微任务),才能触发实时渲染。

内容的提问来源于stack exchange,提问作者A. Mair

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 15:39:21