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

使用useImmer缓存HTMLImageElement时的类型兼容问题求助

解决useImmer缓存HTMLImageElement的类型错误问题

问题描述

为了在Canvas上用drawImage渲染图片,我定义了图片缓存类型并使用useImmer管理状态:

type Cache = {
  image: Record<string, HTMLImageElement>,
};

const initialCache = {
  image: {},
};

父组件代码:

function Component() {
  const [cache, setCache] = useImmer(initialCache);

  return <Child cache={cache} setCache={setCache} />
}

在子组件的异步useEffect中尝试存入新图片时,VSCode抛出类型错误:

Type 'Element' is not assignable to type 'WritableDraft<Element> | WritableDraft<ProcessingInstruction> | null'.

错误出现在这行代码:

props.setCache((d) => {
  d.image[imageKey] = img; // ERROR!
});

核心问题是Immer会尝试代理所有存入draft的对象,但HTMLImageElement这类浏览器宿主对象无法被Immer正确代理,导致类型不匹配和运行时错误。

解决方案

1. 标记图片对象为不可代理

Immer提供了immerable符号,用来标记不需要代理的对象。给Image实例添加这个标记后,Immer会直接存储原对象,不创建代理:

if (!img) {
  img = new Image();
  // 标记该对象无需Immer代理
  (img as any)[Symbol.for('immerable')] = false;
  img.src = "Some base64 string";

  props.setCache((d) => {
    d.image[imageKey] = img;
  });
}

2. 用Immutable方式更新缓存

不直接修改draft,而是返回新的缓存结构,让Immer只处理普通对象的更新,跳过Image对象的代理:

props.setCache((d) => ({
  ...d,
  image: {
    ...d.image,
    [imageKey]: img
  }
}));

3. 用useRef单独存储图片缓存

如果图片缓存的更新不需要触发组件重渲染,可以直接用useRef存储,完全绕开Immer的代理机制:

// 父组件
const imageCacheRef = useRef<Record<string, HTMLImageElement>>({});

return <Child imageCache={imageCacheRef.current} />;

// 子组件
if (!img) {
  img = new Image();
  img.src = "Some base64 string";
  props.imageCache[imageKey] = img;
}

内容的提问来源于stack exchange,提问作者Matija Sirk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 02:27:38