在运行时使用 Ant Design 在 React Gatsby 应用程序中动态更改 Less 变量

Dynamically changing Less variables in React Gatsby app with Ant Design at runtime

我们正在使用 React、GatsbyJs 和 Ant Design 构建一个白标平台。我们坚持使用 Gatsby 和 Ant Design,因为我们正在从现有系统迁移并且更改其中任何一个都会带来巨大的影响。此外,我们必须有一个单一的部署。不能为每个白标构建一个版本。

因此,我们需要能够在运行时更改样式(主要是颜色)。

问题是:Ant Design 使用 less 变量来定义它的主题,我们无法在运行时更改它们,即使使用 less 的 modifyVars 也是如此。

问题是 我们必须更改较少的变量,而不是全局变量 CSS 或使用其他方法

Ant Design对主变量多次求导得到相邻的属性。因此,例如,如果我们将@primary-color 定义为红色,当我们在屏幕上添加一个Button 时,Ant Design 还定义了它的边框颜色、悬停颜色和许多其他具有不同深浅的红色细节。

这意味着,如果我们要使用其他样式工具,我们将需要生成这些颜色派生并替换每个组件的每个 属性。这会很混乱。

场景

我们正在使用 gatsby-plugin-antdgatsby-plugin-less 来减少加载并在构建时更改变量。我们的 gatsby-config.js 看起来像这样:

module.exports = {
  siteMetadata: {
    siteUrl: 'https://www.yourdomain.tld',
    title: 'yourtitle'
  },
  plugins: [
    'gatsby-plugin-root-import',
    'gatsby-plugin-typescript',
    {
      resolve: 'gatsby-plugin-antd',
      options: {
        style: true
      }
    },
    {
      resolve: 'gatsby-plugin-less',
      options: {
        lessOptions: {
          javascriptEnabled: true,
          modifyVars: {
            'primary-color': '#FFFFFF',
            'link-color': '#000000',
            'success-color': '#FFFFFF',
            'warning-color': '#000000'
          }
        }
      }
    }
  ]
};

我们在 gatsby-browser.js 文件中导入样式:

import './src/styles/index';

我们的 styles/index 有:

import 'tachyons';
import './global.css';
import './antd.less';

antd.less:

@import '~antd/dist/antd.less';

并且 global.css 对项目有一些通用 CSS。

它在构建时使用定义的变量工作正常。

到目前为止我们尝试了什么...

我们试用了这个插件: https://github.com/mzohaibqc/antd-theme-webpack-plugin 据推测,这正是我们需要的。但是没有使用 Gatsby 的例子。

然后我们尝试使用此处提到的 gatsby-node.js 添加插件: https://www.gatsbyjs.com/docs/how-to/custom-configuration/add-custom-webpack-config/

首先,我们尝试使用 index.html 作为插件的 indexFileName。就是不行。

然后,按照插件文档,我们尝试将 indexFileName 设置为 false,并在 pages/index.tsx:

使用 Helmet 导入以下脚本
<script> window.less = { async: false, env: 'production' }; 
</script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script> ```

同样无效。如果我们将 indexFileName 定义为 false,我们将在堆上获得内存。 如果我们将 indexFileName 保持为 'index.html' 并只添加脚本,我们就能够调用 window.less.modifyVars 并且它 returns 成功(我们正在记录 Promise 的 thenerror) 但它不影响 antd 的变量。

然后我们尝试做一些类似的事情,但不是从外部加载更少,而是将其安装为 node_module 并将其导入文件并以类似的方式直接使用它。得到相同的结果:modifyVars 运行并且 returns 成功但不影响 antd.

然后,我们尝试了一些不同的东西:我们删除了 gatsby 插件并尝试直接从 antd 导入 less,如下所示: https://ant.design/docs/react/customize-theme 所以我们这样导入它:

@import '~antd/lib/style/themes/default.less';
@import '~antd/dist/antd.less';
@import 'your-theme-file.less';

还有,不好。它与前面的场景不同,因为样式会在您保存代码后更新。无需停止 Gatsby,作为第一个解决方案。但是,modifyVars对antd组件还是没有影响。

然后,为了隔离问题,我们尝试设计一个基本的 HTML 组件——一个按钮——来检查问题是出在 gatsby 还是 antd 上。而且......仍然没有成功。 less.modifyVars 无法在运行时更改基本按钮样式。

所以,我们认为它可能介于 Gatsby 和 Less 之间。我们检查了 gatsby-plugin-antdgatsby-plugin-less 看看我们是否能找到一些东西,但没有找到任何有用的东西。

我们假设 gatsby 的 less-loader 在构建时使用的“less instance”或“less context”与我们调用 modifyVars 的不一样。所以它不会影响原始变量。

完全卡住了。请帮忙!

编辑 - 解决方案

Ant Design 团队刚刚发布了 - TODAY - 一个包含动态主题、使用 CSS 变量的新 alpha 版本。

https://ant.design/docs/react/customize-theme-variable

目前为止一切正常。关闭问题。

编辑 2

已接受的答案有更详细的解决方案。

Ant Design 团队刚刚发布了 - TODAY - 一个包含动态主题、使用 CSS 变量的新 alpha 版本。

https://ant.design/docs/react/customize-theme-variable

到目前为止一切正常。

编辑 - 详细解决方案

我从项目中删除了 gatsby-plugin-antd 和 gatsby-plugin-less。也去掉了antd less文件的导入。

相反,在我的 styles/index.tsx(在 gatsby-browser.js 中导入)中,我正在导入 variables.min.css 文件:

import 'antd/dist/antd.variable.min.css';

然后,每当我想更改 Ant Design 变量时,我只需使用:

import { ConfigProvider } from 'antd';

...

ConfigProvider.config({
  theme: {
    primaryColor: '#[DESIRED_COLOR_HEX]'
  }
});

提供商

由于每次加载站点时都必须执行此操作,因此我创建了一个 ThemeProvider 来包装每个页面并定义主题。它从后端获取主题数据并设置 Ant Design 主题变量。

示例代码:


import { Spin } from 'antd';
import React, { useEffect, useState } from 'react';
import { ConfigProvider } from 'antd';
import { Theme } from './theme.interface';

interface Props {
  children: React.ReactNode;
}

export const ThemeProvider = ({ children }: Props): JSX.Element => {
  const [themeVars, setThemeVars] = useState<Theme>(null);

  useEffect(() => {
    async function fetchMyAPI() {
      const result = await getThemeFromBackend(); // Make API call with Axios
      if (result) setThemeVars(result);
    }
    fetchMyAPI();
  }, []);

  useEffect(() => {
    if (themeVars) {
      ConfigProvider.config({
        theme: {
          primaryColor: `#${themeVars.primaryColor}`
        }
      });
    }
  }, [themeVars]);

  return <div>{!themeVars ? <Spin size="large" /> : <>{children}</>}</div>;
};

并且可以这样使用:

...
<ThemeProvider>
  <h1>My page header</h1>
  <p>Page content...</p>
</ThemeProvider>
...

注意:您可以将主题数据保存在本地存储中以提高性能,这样您就不必在每次重新加载网站时都调用后端。也许你只需要不时刷新它。