我正在构建一个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>
...
每个页面都使用下面的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;
Button.module.scss
文件仅将按钮的背景色设置为红色。
.btn {
background: red;
}
Button
组件接受一个className
道具,然后将其添加到呈现的按钮中。这是因为我想给组件的使用者以自由。例如,在某些页面中可能需要空白,或者背景应该是黄色而不是 red 。
为简单起见,我只想根据当前页面为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];
其中[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
}
很好。然后,我单击导航链接打开关于页面。即使在这种情况下,按钮也正确显示为黄色背景。检查加载的CSS块,其中包含:
.Button_btn__2cUFR {
background: red
}
.About_pageBtn__3jjV7 {
background: yellow
}
当我回到 Home 页面时,该按钮现在显示为红色背景而不是黄色。这是因为 About 页面已加载上面的CSS,该CSS再次定义了Button_btn__2cUFR
类。由于该类现在位于Home_pageBtn__nnyWK
类定义的之后,因此该按钮显示为红色。
注意:由于Button
组件的大小太小,因此不会在 common 块上导出。将其放在一个通用块中可以解决问题。但是,我的问题是关于小型共享组件的。
我考虑过两种解决方案,但是我不太喜欢:
在[PageName].module.scss
中指定的类可以定义为:
.pageBtn.pageBtn {
background: [color];
}
这将提高选择器的特异性,并将覆盖默认的Button_btn__2cUFR
类。但是,如果每个页面块都非常小(小于30kb),则每个页面块都将包含共享的组件。另外,组件的使用者必须知道该技巧。
弹出应用程序(或使用类似react-app-rewired的东西)将允许使用webpack为公共块指定最小大小。但是,这不是我想要的所有组件。
总而言之,问题是:使用延迟加载的路由时,覆盖共享组件样式的正确工作方式是什么?
您可以将以下逻辑与配置文件一起用于任何页面。另外,您可以从远程服务器(req / res API)发送配置数据,并使用redux进行处理。
查看演示:CodeSandBox
创建components
目录并创建如下文件:
src
|---components
|---Button
| |---Button.jsx
| |---Button.module.css
按钮组件:
// 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;
...
// Button.module.css
.Home_btn {
background: red;
}
.About_btn {
background: blue;
}
创建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;
创建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;
将index.js
和App.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
);
...
react-router-config:React的静态路由配置帮助器 路由器。
// 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>
);
}
创建pages
目录并创建文件和子目录,如下所示:
src
|---pages
|---about
| |---AboutPage.jsx
| |---AboutPageConfig.jsx
|
|---home
|---HomePage.jsx
|---HomePageConfig.jsx
|
|---pagesConfig.js
关于页面文件:
// 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;
...
// 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"))
}
]
};
主页文件:
// 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;
...
// HomePageConfig.jsx
import React from "react";
export const HomePageConfig = {
settings: {
layout: {
config: {
buttonClass: "Home_btn"
}
}
},
routes: [
{
path: "/",
exact: true,
component: React.lazy(() => import("./HomePage"))
}
]
};
...
// pagesConfig.js
import { HomePageConfig } from "./home/HomePageConfig";
import { AboutPageConfig } from "./about/AboutPageConfig";
export const pagesConfig = [HomePageConfig, AboutPageConfig];
编辑部分: 使用 HOC 可能是这样的:CodeSandBox
创建hoc
目录和withPage.jsx
文件:
src
|---hoc
|---withPage.jsx
...
// 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} />;
};
}
然后显示如下页面:
src
|---pages
|---about
| |---About.jsx
| |---About.module.css
|
|---home
|---Home.jsx
|---Home.module.css
About.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");
About.module.css文件:
// About.module.css
.AboutBtn {
background: yellow;
}
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");
Home.module.css文件:
// Home.module.css
.HomeBtn {
background: red;
}
我建议不要添加默认样式和使用者样式,而应使用使用者样式代替您的样式,如果未提供,则将您用作回调。使用者仍然可以使用[0][1]
关键字来编写默认值。
Button.js
composes
SomePage.module.scss
import React from 'react';
import styles from './Button.module.scss';
const Button = ({ children, className}) => (
<button className={className ?? styles.btn}>{children}</button>
);
export default Button;
如果愿意,可以使用sass @extends或@mixin
编辑:尚未测试过,但是仅仅是使用.pageBtn {
// First some defaults
composes: btn from './Button.module.scss';
// And override some of the defautls here
background: yellow;
}
webpack是否可以确保将默认值仅捆绑一次?因此,您不再需要使用composes
我知道这很明显,但是还是可以的:
在覆盖CSS规则上设置!important
,从而绕过特定性:
[PageName].module.scss
:
.btn {
color: yellow !important;
}
但是,我认识的大多数严格的开发人员都会不惜一切代价避免使用此关键字。
为什么?
因为当您开始大量使用!important
时,css是调试的噩梦。如果您开始以更高的特异性编写!important
规则,那么您就知道自己走得太远了
它仅适用于像您这样的极端情况,您不妨使用它。
修复了CRA配置以强制样式标签顺序。
毕竟是开源的:)
您可以在此处输入有关此错误的输入(upvote可能会使其更容易看到):
https://github.com/facebook/create-react-app/issues/7190
您可以在新的customButton.scss
文件中创建SCSS混合,以生成具有更高特异性的CSS规则:
// customButton.scss
@mixin customBtn() {
:global {
.customBtn.override {
@content;
}
}
}
我们将使用两个静态类名(使用:global选择器),因为那样的话,它们的名称不会根据它们从何处导入而改变。
现在在页面的SCSS中使用该混合:
// [pageName].module.scss
@import 'customButton.scss';
@include customBtn {
color: yellow;
}
css输出应为:
.customBtn.override {
// put everything you want to customize here
color: yellow;
}
在Button.jsx
中:除了styles.btn
以外,还将两个类名都应用到您的按钮上:
// Button.jsx
const Button = ({ children, className = '' }) => (
<button className={`${styles.btn} customBtn override ${className}`}>
{children}
</button>
);
(请注意,这些不是通过styles
对象进行引用,而是直接通过类名进行引用)
主要缺点是这些不是动态类名,因此您必须像以前一样小心避免自己发生冲突。 但我认为应该可以解决问题