使用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
相关产品推荐
相关产品推荐

