CSS Modules 实现嵌套样式覆盖
目录背景什么是 CSS Modules?CSS Modules 实现原理问题和解决方法嵌套样式覆盖不生效如何确保覆盖?背景什么是 CSS Modules?CSS Modules 这个词在不同语境下可能有不同意思,这里指的是 这个库。如果你用过 Vue,那么CSS Modules 就非常好理解了。Vue 中可以声明样式为 scoped,也就是说这些样式只作用于当前组件,不会对当前组件外的任何元素生效
背景
什么是 CSS Modules?
CSS Modules
这个词在不同语境下可能有不同意思,这里指的是 这个库
。
如果你用过 Vue,那么CSS Modules
就非常好理解了。Vue 中可以声明样式为 scoped
,也就是说这些样式只作用于当前组件,不会对当前组件外的任何元素生效。这样就可以避免在全局范围内刚好取了相同名字的 CSS 类的冲突问题。可以说,你在 Vue 中写的绝大部分样式都应该是 scoped
。
但 React 本身不具备相当于 Vue 中 scoped
这样的模块化样式功能,所以若想实现同样的效果,CSS Modules 就派上用场了。
CSS Modules 实现原理
CSS Modules 的实现原理与 Vue 的 scoped
不完全相同,但也十分类似。
在 Vue 中,如果你使用了 scoped
样式,那么当前组件会被分配一个独一无二的 ID,类似于 data-v-f3f3eg9
这样,组件下的所有元素都会被加上这个属性。然后 Vue 会修改你的 CSS 规则,在每个选择器上都加上 [data-v-f3f3eg9]
的限制。这样一来,当前组件中的规则对组件之外的元素就不会生效了,因为组件之外的元素不会有同样的属性。
CSS Modules 的实现方法更为直接,就是把你写的每个类的名字修改成一个独一无二的名字。例如,你写了以下 CSS:
.button {
background-color: red;
}
那么编译之后,这个类可能就变成 button_1Aw-M
这样的独一无二的名字了。一般是会加后缀,但也可以配置成不同的规则(我当前所在的项目就特别损,完全生成了一堆乱码,根本看不出来之前对应的类名是什么,特别不方便调试)。
问题和解决方法
假设你要使用一个别人写的组件,该组件已经有默认的样式了,但你希望覆盖其中的部分样式。
该组件代码大致如下,首先是 CSS:
/* base-component.modules.css */
.container {
background-color: red;
.target {
color: white;
}
}
然后 import 到 React 组件中:
// BaseComponent.jsx
...
import styles from './base-component.module.css'
const BaseComponent = () => {
return (
<div className={styles.container}>
<div className={styles.target}></div>
</div>
)
}
这样就实现了对两个嵌套 div
分别的样式指定。
嵌套样式覆盖不生效
如果像下面这样写,是行不通的。首先修改 BaseComponent
:
// BaseComponent.jsx
import styles from './base-component.module.css'
import cx from 'classnames';
const BaseComponent = ({ containerClass }) => {
return (
{/* 注意这里 */}
<div className={cx(styles.container, containerClass)}>
<div className={styles.target}></div>
</div>
)
}
然后新建一个 CSS 文件:
/* my-component.modules.css */
.container {
background-color: green;
.target {
color: black;
}
}
然后导入到自己的 React 组件中:
// MyComponent.jsx
import styles from './my-component.module.css'
import BaseComponent from './BaseComponent.jsx'
const MyComponent = () => {
return (
<BaseComponent containerClass={styles.container} />
)
}
为啥不行呢?新加的 container
类是没问题的,但 container
中嵌套的 target
类却不会生效。因为base-component.module.csss
和你新加的 my-component.module.csss
是两个不同的 CSS Module,虽然都有 target
这个类,但编译之后会有各自不同的类名。如果希望指定 target
类的样式,就必须把 target
的类名也传递过去。
换句话说,只传递最外层的类名是不够的,需要把所有类名都传过去,包括嵌套的类。如果给每个类名都加一个 prop
来传就太混乱了,所以最好直接把 styles
也就是 CSS Module 对象)整个传过去。
例如,修改 BaseComponent.jsx
:
// BaseComponent.jsx
import styles from './base-component.module.css'
import cx from 'classnames';
const BaseComponent = ({ externalStyles }) => {
return (
{/* 注意这里 */}
<div className={cx(styles.container, externalStyles.container)}>
{/* 以及这里 */}
<div className={cx(styles.target, externalStyles.target)}></div>
</div>
)
}
然后在自己的 React 组件中,直接把 styles
传过去:
// MyComponent.jsx
import styles from './my-component.module.css'
import BaseComponent from './BaseComponent.jsx'
const MyComponent = () => {
return (
<BaseComponent externalStyles={styles} />
)
}
如何确保覆盖?
解决了嵌套的问题,还是不能保证覆盖。因为在上面的例子中,新加的样式和原有的样式具有相同的特殊性(Specificity),即,都是通过同样数量的类名选择的。
按理来说,在特殊性相同的情况下,后声明的规则会覆盖先声明的规则;但由于使用了 CSS Modules,你不太好控制哪个规则先声明,哪个后声明。
当然,一个简单的方法是,如果有新样式,直接丢弃旧样式,全部使用新的。但这样的问题是,无法实现“部分覆盖”,需要把本来不需要覆盖的相同部分也重写一遍。这样肯定不好。
其实最终的解决办法很简单,但我在同事的提醒下才恍然大悟。为了提高新样式的特殊性,可以额外加一层 div
:
// MyComponent.jsx
import styles from './my-component.module.css'
import BaseComponent from './BaseComponent.jsx'
const MyComponent = () => {
return (
<div className=styles.wrapper>
<BaseComponent externalStyles={styles} />
</div>
)
}
相应的 CSS:
/* my-component.modules.css */
.wrapper {
.container {
background-color: green;
.target {
color: black;
}
}
}
这样就可以确保新样式可以覆盖旧样式了!
更多推荐
所有评论(0)