使用dangerouslySetInnerHTML渲染脚本出现Hydration错误求助
没法让ImageMapPro脚本正常运行,只能靠dangerouslySetInnerHTML才能让插件在前端显示,不然控制台会报ImageMapPro未定义。但插件正常显示一秒后,控制台弹出Hydration错误:
app-index.js:31 Warning: Prop
dangerouslySetInnerHTMLdid not match. Server: "\n <div id="image-map-pro" data-image-map-id="5256f3ec...\n
ImageMap.jsx
'use client' import { useEffect, useState } from "react"; export default function ImageMapPro() { const [render, setRender] = useState(false); const script = ` <div id="image-map-pro"></div> <script src="image-map-pro/image-map-pro.min.js"></script> <script>ImageMapPro.init('#image-map-pro', {"id":"5256f3ec-3b42-4614-bd33-7332a713cac7","artboards":[{"children":[{"id":"27b1e6ba-0de4-49a0-a732-0d6824f84d17","title":"Ellipse","type":"oval","x":9.433962264150944,"y":32.239583333333336,"width":50.35377358490566,"height":54.583333333333336,"default_style":{"background_color":"#ff0000"},"x_image_background":9.433962264150944,"y_image_background":32.239583333333336},{"id":"1e6ab53b-a4c2-4a2f-a2fd-6fa8df37c642","title":"Rect","type":"rect","x":22.995283018867923,"y":18.90625,"width":61.910377358490564,"height":55,"tooltip_content":[{"type":"Heading","text":"test","heading":"h3","other":{"id":"","classes":"","css":""},"style":{"fontFamily":"sans-serif","fontSize":20.8,"lineHeight":"normal","color":"#ffffff","textAlign":"left"},"boxModel":{"width":"auto","height":"auto","margin":{"top":0,"bottom":0,"left":0,"right":0},"padding":{"top":10,"bottom":10,"left":10,"right":10}},"id":"ffbed5ee-255b-41a6-8229-c3f6f693497b"}],"x_image_background":22.995283018867923,"y_image_background":18.90625}]}],"version":"6.0.8","general":{"name":"Untitled"}})</script> ` useEffect(() => { setRender(true); }, []); return ( <div dangerouslySetInnerHTML={{ __html: script }}></div> ); }
page.jsx
return ( <> <ImageMapPro /> </> );
Hydration错误本质是服务端渲染出的HTML和客户端 hydration 后的HTML不一致。你用dangerouslySetInnerHTML插入内容时,服务端只会渲染初始的<div id="image-map-pro"></div>,但客户端执行脚本后,ImageMapPro会修改这个div(比如添加data-image-map-id属性),导致前后内容不匹配,触发警告。
方案1:控制组件仅在客户端渲染
既然已经加了'use client',直接用render状态控制组件只在客户端渲染,服务端返回空内容,避免前后差异:
修改ImageMap.jsx的return部分:
return render ? ( <div dangerouslySetInnerHTML={{ __html: script }}></div> ) : null;
这样服务端不会渲染任何插件相关内容,客户端触发useEffect后才加载,不会有Hydration不匹配问题。
方案2:手动操作DOM替代dangerouslySetInnerHTML
直接用React的DOM API创建元素和加载脚本,更安全,也彻底避免Hydration问题:
'use client' import { useEffect, useRef } from "react"; export default function ImageMapPro() { const containerRef = useRef(null); useEffect(() => { if (!containerRef.current) return; // 创建插件容器 const mapDiv = document.createElement('div'); mapDiv.id = 'image-map-pro'; containerRef.current.appendChild(mapDiv); // 加载插件脚本 const script = document.createElement('script'); script.src = 'image-map-pro/image-map-pro.min.js'; script.onload = () => { // 脚本加载完成后初始化插件 ImageMapPro.init('#image-map-pro', { "id":"5256f3ec-3b42-4614-bd33-7332a713cac7", "artboards":[{"children":[{"id":"27b1e6ba-0de4-49a0-a732-0d6824f84d17","title":"Ellipse","type":"oval","x":9.433962264150944,"y":32.239583333333336,"width":50.35377358490566,"height":54.583333333333336,"default_style":{"background_color":"#ff0000"},"x_image_background":9.433962264150944,"y_image_background":32.239583333333336},{"id":"1e6ab53b-a4c2-4a2f-a2fd-6fa8df37c642","title":"Rect","type":"rect","x":22.995283018867923,"y":18.90625,"width":61.910377358490564,"height":55,"tooltip_content":[{"type":"Heading","text":"test","heading":"h3","other":{"id":"","classes":"","css":""},"style":{"fontFamily":"sans-serif","fontSize":20.8,"lineHeight":"normal","color":"#ffffff","textAlign":"left"},"boxModel":{"width":"auto","height":"auto","margin":{"top":0,"bottom":0,"left":0,"right":0},"padding":{"top":10,"bottom":10,"left":10,"right":10}},"id":"ffbed5ee-255b-41a6-8229-c3f6f693497b"}],"x_image_background":22.995283018867923,"y_image_background":18.90625}]}], "version":"6.0.8", "general":{"name":"Untitled"} }); }; document.body.appendChild(script); // 组件卸载时清理资源 return () => { containerRef.current.innerHTML = ''; script.remove(); // 如果插件提供了销毁方法,这里可以调用,比如:ImageMapPro.destroy('#image-map-pro'); }; }, []); return <div ref={containerRef}></div>; }
这种方式完全在客户端执行DOM操作,服务端不会渲染任何插件相关内容,从根源上解决Hydration不匹配问题,同时比dangerouslySetInnerHTML更安全,降低XSS风险。
额外提示
- 确认
image-map-pro.min.js的路径正确,客户端能正常访问到该文件。 - 如果ImageMapPro有官方React集成包,优先使用官方方案,避免手动操作DOM。
内容的提问来源于stack exchange,提问作者Jimmy

