[英]Dynamically load and unload content of css file in javascript/react
几天来,我一直在努力解决这个问题。 我阅读了所有类似的问题,但它们并不完全符合我的需要。
我很难相信以前没有人尝试过或记录过这一点,我很想对如何解决这个问题提出一些想法。
要求
我正在为我的应用程序使用 react bootstrap,我想让用户在一组 bootstrap 主题之间进行选择。 为了简单起见,我目前只有一个浅色和深色。 这些 styles 从https://bootswatch.com/作为 scss 发货,所以我宁愿不尝试在 JS 主题方法中使用通常的 CSS。
我正在尝试做的事情
理想情况下,我想要的是有条件地渲染/导入 css 文件的能力。 我已经尝试了一些东西,其中大多数都不起作用,其中一种起作用,但真的很hacky。
我想要的是一种使用 css 文件中的样式节点构造 DOM 节点而不渲染它的方法。 然后我可以将所有这些样式节点放入 DOM 并有选择地添加/删除它们。
理想情况下,我根本不需要手动更改 DOM。 我一直无法找到一种方法来做到这一点。 我希望我的样式组件的条件渲染能够解决问题,但这不起作用,因为样式永远不会加载(超过第一次)或卸载(永远)。
到目前为止我尝试了什么
使用延迟加载(有点工作)
我将我的 css 放在一个轻量级组件中,例如
import React from 'react';
import './_light.scss';
const Theme = React.forwardRef(( props, ref) => (<div ref={ref}></div>));
export default Theme;```
然后我像这样延迟加载或在我的应用程序中加载
const LightTheme = React.lazy(() => import('../themes/lightTheme'));
const DarkTheme = React.lazy(() => import('../themes/darkTheme'));
....
const element = (chosenTheme === PreferredTheme.LIGHT) ?
<LightTheme /> : <DarkTheme />
...
return (
<ThemeContext.Provider value={initialValue}>
<React.Suspense fallback="{<></>}">
{element}
</React.Suspense>
{children}
</ThemeContext.Provider>
)
问题是我让用户选择切换主题,似乎发生的是两个问题 1)一旦两者都加载(一次),他们永远不会再次加载 2)样式节点的顺序很重要,所以无论最后加载哪个总是保持活跃。
我可以通过在我的 useEffect 中进行一些 hacky <head>
节点操作来解决这个问题,以删除(不再使用的样式)/添加(将应用)样式节点到 DOM,但它充其量只是 hacky。
使用参考
要获取对每个样式节点的引用,请将其保存到 dict 中,然后将其删除。 这以一种有趣的方式中断,其中引用在事物第一次渲染时起作用,但在随后的渲染中,引用变得undefined
即时创建样式表
这里的解决方案看起来很有希望如何动态加载/卸载 css 。 问题是在这种情况下,我不知道如何获取样式表的inner text
。 正如我所说,它是作为 scss 发货的,所以它会被预处理,我什至不知道如何才能得到文本。
任何想法或解释都非常感谢。
是的,所以我现在找到了一个非常简洁的解决方案,至少具有简单的逻辑。 我必须更多地了解 Webpack 以及它的装载机是如何工作的。 特别是如何使用 CRA 并且仍然能够修改它的行为。 长话短说,CRA webpack 将样式加载器链接到 scss 文件的末尾,因此尝试将 css 作为节点放入 DOM ( Z5E056C500A1C4B6A7110B50D806loader.style-Z://pack jsorg/7BADE5.Z://pack )
因为我还不想这样做,所以我需要禁用由 import 语句的这种可恶处理的样式加载器( https://webpack.js.org/concepts/loaders/#inline )
// @ts-ignore
import { default as DarkThemeC } from '!css-loader!sass-loader!../themes/_dark.scss'; // eslint-disable-line import/no-webpack-loader-syntax
// @ts-ignore
import { default as LightThemeC } from '!css-loader!sass-loader!../themes/_light.scss'; // eslint-disable-line import/no-webpack-loader-syntax
现在我将 css 作为数组供我使用,无需渲染,因此我将其放入 Map 以备将来使用。
const availableThemes = new Map<PreferredTheme, string>();
availableThemes.set(PreferredTheme.LIGHT, LightThemeC.toString());
availableThemes.set(PreferredTheme.DARK, DarkThemeC.toString());
这意味着我可以在我的 useEffect 回调中执行此操作
useEffect( () => {
// Add new sheet
const newTheme = availableThemes.get(chosenTheme);
if(newTheme) {
// injectCss borrowed from https://stackoverflow.com/questions/60593614/how-to-load-unload-css-dynamically
injectCss(newTheme, chosenTheme);
}
// Remove all others
availableThemes.forEach( (style, theme) => {
if(theme !== chosenTheme) {
var stylesheet = document.getElementById(`dynamicstylesheet${theme}`);
if (stylesheet) {
head.removeChild(stylesheet);
}
}
});
}, [chosenTheme])
像魅力一样工作,但我观察到的唯一一件事是开关有点慢(几分之一秒)
为什么不将主题指定为 class 到应用程序的顶级元素,并使用 CSS 变量在其下构建 CSS 类?
#app.dark {
--app-bg: black;
--app-fg: white;
}
#app.light {
--app-bg: white
--app-fg: black;
}
#app {
background-color: var(--app-bg);
color: var(--app-fg);
}
如果值得的话,您确实可以使用高阶组件来包装您的 UI,并根据所选主题更改它,并使用Webpack 进行延迟加载。 FWIW,我的经验是深色/浅色主题最简单,也最清楚地处理如上。
这是我的解决方案(看起来像@sev 解决方案)
import { useEffect } from 'react';
export const useDynamicStyleSheet = (styleSheet: string): void => {
useEffect(() => {
const styleElement = document.createElement('style');
styleElement.innerHTML = styleSheet;
document.head.append(styleElement);
return () => styleElement.remove();
}, [styleSheet]);
};
import { FC } from 'react';
import { useDynamicStyleSheet } from 'app/hooks/useDynamicStyleSheet';
// eslint-disable-next-line import/no-webpack-loader-syntax, import/no-unresolved
import lightTheme from '!css-loader!antd/dist/antd.css';
const LightTheme: FC = () => {
useDynamicStyleSheet(lightTheme.toString());
return null;
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.