繁体   English   中英

在javascript/react中动态加载和卸载css文件的内容

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM