如何自定义延迟加载页面之间共享的 React 组件的样式?

Oma*_*llo 6 javascript sass reactjs create-react-app

我正在构建一个 React 应用程序,并开始使用CRA。我使用React Router配置了应用程序的路由。页面组件是延迟加载的。

有 2 页:主页关于

...
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));

...
      <Suspense fallback={<div>Loading...</div>}>
        <Switch>
          <Route path="/about" component={About} />
          <Route path="/" component={Home} />
        </Switch>
      </Suspense>
...
Run Code Online (Sandbox Code Playgroud)

每个页面都使用Button下面的组件。

import React from 'react';
import styles from './Button.module.scss';

const Button = ({ children, className = '' }) => (
    <button className={`${styles.btn} ${className}`}>{children}</button>
);

export default Button;
Run Code Online (Sandbox Code Playgroud)

Button.module.scss文件只是将按钮的背景颜色设置为red

.btn {
    background: red;
}
Run Code Online (Sandbox Code Playgroud)

Button组件接受一个className道具,然后将其添加到呈现的按钮中。这是因为我想为组件的消费者提供自由。例如,在某些页面中可能需要边距或背景应该是黄色而不是红色

为简单起见,我只想为Button基于当前页面的 设置不同的背景颜色,以便:

  • 主页 => 蓝色按钮
  • 关于页面 => 黄色按钮

每个页面定义如下:

import React from 'react';
import Button from './Button';
import styles from './[PageName].module.scss';

const [PageName] = () => (
    <div>
        <h1>[PageName]</h1>
        <Button className={styles.pageBtn}>[ExpectedColor]</Button>
    </div>
);

export default [PageName];
Run Code Online (Sandbox Code Playgroud)

其中[PageName]是页面名称,[ExpectedColor]是基于上述项目符号列表(蓝色或黄色)的相应预期颜色。

导入的 SCSS 模块导出一个类.pageBtn,该类将background属性设置为所需的颜色。

注意:我可以在Button组件上使用定义要显示的变体(蓝色/黄色)的道具,并基于该道具添加在 SCSS 文件中定义的类。我不想这样做,因为更改可能不属于变体(例如margin-top)。

问题

如果我使用 运行该应用程序yarn start,该应用程序工作正常。但是,如果我构建应用程序 ( yarn build) 然后我开始为应用程序提供服务(例如使用serve -s build),则行为会有所不同并且应用程序不会按预期工作。

主页加载页面时,该按钮被正确地以蓝色背景显示。检查加载的 CSS 块,它包含:

.Button_btn__2cUFR {
    background: red
}

.Home_pageBtn__nnyWK {
    background: blue
}
Run Code Online (Sandbox Code Playgroud)

没关系。然后我点击导航链接打开关于页面。即使在这种情况下,该按钮也会以黄色背景正确显示。检查加载的 CSS 块,它包含:

.Button_btn__2cUFR {
    background: red
}

.About_pageBtn__3jjV7 {
    background: yellow
}
Run Code Online (Sandbox Code Playgroud)

当我返回主页时,该按钮现在显示为红色背景而不是黄色。那是因为关于页面已经加载了上面的 CSS,它再次定义了Button_btn__2cUFR类。由于类现在是以后Home_pageBtn__nnyWK类定义,该按钮显示为红色。

注:Button组件没有出口的共同块,因为它的尺寸太小。将它放在一个公共块中可以解决问题。但是,我的问题是关于小型共享组件。

解决方案

我想过 2 个解决方案,但是,我不太喜欢:

增加选择器的特异性

中指定的类[PageName].module.scss可以定义为:

.pageBtn.pageBtn {
   background: [color];
}
Run Code Online (Sandbox Code Playgroud)

这将增加选择器的特异性并将覆盖默认Button_btn__2cUFR类。但是,如果组件非常小(小于30kb),则每个页面块都将包含共享组件。此外,组件的使用者必须知道这个技巧。

弹出并配置 webpack

弹出应用程序(或使用类似react-app- rewired 的东西)将允许使用 webpack 指定公共块的最小大小。但是,这并不是我想要的所有组件。


总而言之,问题是:在使用延迟加载路由时覆盖共享组件样式的正确工作方式是什么?

Moh*_*deh 5

您可以在任何页面的配置文件中使用以下逻辑。此外,您可以从远程服务器(req/res API)发送配置数据并使用 redux 进行处理。

见演示:CodeSandBox

创建components目录并创建如下文件:

src
 |---components
      |---Button
      |     |---Button.jsx
      |     |---Button.module.css
Run Code Online (Sandbox Code Playgroud)

按钮组件:

// Button.jsx

import React from "react";
import styles from "./Button.module.css";

const Button = props => {
  const { children, className, ...otherProps } = props;
  return (
    <button className={styles[`${className}`]} {...otherProps}>
      {children}
    </button>
  );
};

export default Button;
Run Code Online (Sandbox Code Playgroud)

...

// Button.module.css

.Home_btn {
  background: red;
}
.About_btn {
  background: blue;
}
Run Code Online (Sandbox Code Playgroud)

创建utils目录并创建AppUtils.js文件:

该文件处理页面的配置文件并返回新对象

class AppUtils {
  static setRoutes(config) {
    let routes = [...config.routes];

    if (config.settings) {
      routes = routes.map(route => {
        return {
          ...route,
          settings: { ...config.settings, ...route.settings }
        };
      });
    }

    return [...routes];
  }

  static generateRoutesFromConfigs(configs) {
    let allRoutes = [];
    configs.forEach(config => {
      allRoutes = [...allRoutes, ...this.setRoutes(config)];
    });
    return allRoutes;
  }
}

export default AppUtils;
Run Code Online (Sandbox Code Playgroud)

创建app-configs目录并创建routesConfig.jsx文件:

该文件列出并组织路线。

import React from "react";

import AppUtils from "../utils/AppUtils";
import { pagesConfig } from "../pages/pagesConfig";

const routeConfigs = [...pagesConfig];

const routes = [
  ...AppUtils.generateRoutesFromConfigs(routeConfigs),
  {
    component: () => <h1>404 page not found</h1>
  }
];

export default routes;
Run Code Online (Sandbox Code Playgroud)

修改index.jsApp.js文件为:

// index.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
  rootElement
);
Run Code Online (Sandbox Code Playgroud)

...

react-router-config:React Router 的静态路由配置助手。

// App.js

import React, { Suspense } from "react";
import { Switch, Link } from "react-router-dom";
import { renderRoutes } from "react-router-config";

import routes from "./app-configs/routesConfig";

import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ul>
      <Suspense fallback={<h1>loading....</h1>}>
        <Switch>{renderRoutes(routes)}</Switch>
      </Suspense>
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

创建pages目录并创建文件和子目录,如下所示:

src
 |---pages
      |---about
      |     |---AboutPage.jsx
      |     |---AboutPageConfig.jsx
      |
      |---home
           |---HomePage.jsx
           |---HomePageConfig.jsx
      |
      |---pagesConfig.js
Run Code Online (Sandbox Code Playgroud)

关于页面文件:

// AboutPage.jsx

import React from "react";
import Button from "../../components/Button/Button";

const AboutPage = props => {
  const btnClass = props.route.settings.layout.config.buttonClass;
  return (
    <>
      <h1>about page</h1>
      <Button className={btnClass}>about button</Button>
    </>
  );
};

export default AboutPage;
Run Code Online (Sandbox Code Playgroud)

...

// AboutPageConfig.jsx

import React from "react";

export const AboutPageConfig = {
  settings: {
    layout: {
      config: {
        buttonClass: "About_btn"
      }
    }
  },
  routes: [
    {
      path: "/about",
      exact: true,
      component: React.lazy(() => import("./AboutPage"))
    }
  ]
};
Run Code Online (Sandbox Code Playgroud)

主页文件:

// HomePage.jsx

import React from "react";
import Button from "../../components/Button/Button";

const HomePage = props => {
  const btnClass = props.route.settings.layout.config.buttonClass;
  return (
    <>
      <h1>home page</h1>
      <Button className={btnClass}>home button</Button>
    </>
  );
};

export default HomePage;
Run Code Online (Sandbox Code Playgroud)

...

// HomePageConfig.jsx

import React from "react";

export const HomePageConfig = {
  settings: {
    layout: {
      config: {
        buttonClass: "Home_btn"
      }
    }
  },
  routes: [
    {
      path: "/",
      exact: true,
      component: React.lazy(() => import("./HomePage"))
    }
  ]
};
Run Code Online (Sandbox Code Playgroud)

...

// pagesConfig.js

import { HomePageConfig } from "./home/HomePageConfig";
import { AboutPageConfig } from "./about/AboutPageConfig";

export const pagesConfig = [HomePageConfig, AboutPageConfig];
Run Code Online (Sandbox Code Playgroud)

编辑部分: 使用HOC也许这样:CodeSandBox

创建hoc目录和withPage.jsx文件:

src
 |---hoc
      |---withPage.jsx
Run Code Online (Sandbox Code Playgroud)

...

// withPage.jsx

import React, { useEffect, useState } from "react";

export function withPage(Component, path) {
  function loadComponentFromPath(path, setStyles) {
     import(path).then(component => setStyles(component.default));
   }
  return function(props) {
    const [styles, setStyles] = useState();
    
    useEffect(() => {
      loadComponentFromPath(`../pages/${path}`, setStyles);
    }, []);
    return <Component {...props} styles={styles} />;
  };
}
Run Code Online (Sandbox Code Playgroud)

然后页面如下:

src
 |---pages
      |---about
      |     |---About.jsx
      |     |---About.module.css
      |
      |---home
           |---Home.jsx
           |---Home.module.css
Run Code Online (Sandbox Code Playgroud)

关于.jsx文件:

// About.jsx

import React from "react";
import { withPage } from "../../hoc/withPage";

const About = props => {
  const {styles} = props;
  return (
    <button className={styles && styles.AboutBtn}>About</button>
  );
};

export default withPage(About, "about/About.module.css");
Run Code Online (Sandbox Code Playgroud)

关于.module.css 文件:

// About.module.css

.AboutBtn {
  background: yellow;
}
Run Code Online (Sandbox Code Playgroud)

Home.jsx 文件:

// Home.jsx

import React from "react";
import { withPage } from "../../hoc/withPage";

const Home = props => {
  const { styles } = props;
  return <button className={styles && styles.HomeBtn}>Home</button>;
};

export default withPage(Home, "home/Home.module.css");
Run Code Online (Sandbox Code Playgroud)

Home.module.css 文件:

// Home.module.css

.HomeBtn {
  background: red;
}
Run Code Online (Sandbox Code Playgroud)


Mor*_*hai 1

我建议不要同时添加默认样式和消费者样式,而是使用消费者的样式而不是您的样式,并在未提供时使用您的回调。消费者仍然可以使用composes关键字编写默认值。

按钮.js

import React from 'react';
import styles from './Button.module.scss';

const Button = ({ children, className}) => (
    <button className={className ?? styles.btn}>{children}</button>
);

export default Button;
Run Code Online (Sandbox Code Playgroud)

SomePage.module.scss

.pageBtn {
  // First some defaults
  composes: btn from './Button.module.scss';
  // And override some of the defautls here
  background: yellow;
}
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以使用 sass @extends 或 @mixin 代替

编辑:还没有测试过,但是是否仅通过使用composeswebpack 就可以确保仅捆绑默认值一次?因此,您不再需要使用以下命令更改 Button.js 代码??