react-select渲染至window.open新窗口时样式未加载问题
问题原因
react-select 从v5版本开始默认基于emotion实现CSS-in-JS样式注入,所有组件样式默认会被插入到当前父页面的document.head节点中。通过window.open创建的新窗口是完全独立的浏览器上下文,拥有自己独立的document对象,父页面中注入的样式规则不会自动同步到新窗口的document内,因此渲染到新窗口中的Select组件无法匹配到对应样式,出现样式丢失的问题。
解决方案
以下两种方案都可以解决该问题,可根据项目场景选择:
- 方案1:全量复制父页面样式到新窗口,通用性强,所有在父页面生效的组件样式都能自动在新窗口生效,不需要单独适配单个组件库
- 方案2:为react-select单独指定样式注入容器,精准无冗余,只会把Select相关的样式注入到新窗口,性能更优
方案1实现代码
修改新窗口组件window.tsx,在新窗口初始化完成后,将父页面的所有内联样式、外链样式表复制到新窗口的head节点中:
// window.tsx import React, { useState, useRef, useEffect, ReactNode } from "react"; import ReactDOM from "react-dom"; interface WindowComponentProps { children: ReactNode; } const WindowComponent = ({ children }: WindowComponentProps) => { const windowRef = useRef<Window | null>(null); const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null); useEffect(() => { const newWindow = window.open("", "", "width=1200,height=400"); if (!newWindow) return; windowRef.current = newWindow; // 样式复制逻辑 const copyStyles = (sourceDoc: Document, targetDoc: Document) => { // 复制内联style标签样式 Array.from(sourceDoc.styleSheets).forEach(sheet => { try { if (sheet.cssRules) { const newStyleEl = sourceDoc.createElement("style"); Array.from(sheet.cssRules).forEach(rule => { newStyleEl.append(rule.cssText); }); targetDoc.head.appendChild(newStyleEl); } } catch (err) { // 跨域样式表无法读取cssRules,跳过即可,后续通过克隆link标签加载 } }); // 复制外链样式表 Array.from(sourceDoc.querySelectorAll('link[rel="stylesheet"]')).forEach(link => { targetDoc.head.appendChild(link.cloneNode(true)); }); }; newWindow.addEventListener("load", () => { copyStyles(document, newWindow.document); const container = newWindow.document.createElement("div"); newWindow.document.body.appendChild(container); setContainerElement(container); newWindow.document.title = "Graph"; }); return () => { newWindow.close(); }; }, []); return ( <div> {containerElement && ReactDOM.createPortal(children, containerElement)} </div> ); }; export default WindowComponent;
方案2实现代码
通过emotion提供的CacheProvider,手动指定react-select的样式注入目标为新窗口的head节点。需要先安装依赖:npm i @emotion/react @emotion/cache,之后修改window.tsx:
// window.tsx import React, { useState, useRef, useEffect, ReactNode } from "react"; import ReactDOM from "react-dom"; import createCache from "@emotion/cache"; import { CacheProvider } from "@emotion/react"; interface WindowComponentProps { children: ReactNode; } const WindowComponent = ({ children }: WindowComponentProps) => { const windowRef = useRef<Window | null>(null); const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null); const [styleCache, setStyleCache] = useState<ReturnType<typeof createCache> | null>(null); useEffect(() => { const newWindow = window.open("", "", "width=1200,height=400"); if (!newWindow) return; windowRef.current = newWindow; const container = newWindow.document.createElement("div"); newWindow.document.body.appendChild(container); setContainerElement(container); newWindow.document.title = "Graph"; // 创建指向新窗口head的emotion样式缓存 const cache = createCache({ key: "react-select", container: newWindow.document.head }); setStyleCache(cache); return () => { newWindow.close(); }; }, []); return ( <div> {containerElement && styleCache && ReactDOM.createPortal( <CacheProvider value={styleCache}> {children} </CacheProvider>, containerElement )} </div> ); }; export default WindowComponent;
注意:如果新窗口中还使用了其他基于CSS-in-JS实现的组件库,只要该组件库基于emotion开发,都可以通过方案2的方式自动适配样式注入,不需要额外修改组件代码。
内容的提问来源于stack exchange,提问作者gglasses
相关产品推荐
相关产品推荐

