引言
问题:CSS 文件分离 != CSS 作用域隔离
看下这样的目录结构:
├── src │ ├──...... # 公共组件目录│ ├── components # 组件│ │ └──comA # 组件A│ │ ├──comA.js │ │ ├──comA.css │ │ └── index.js │ │ └──comB # 组件B│ │ ├──comB.js │ │ ├──comB.css │ │ └── index.js │ ├── routes # 页面模块 │ │ └── modulesA # 模块A│ │ ├──pageA.js # pageA JS 代码│ │ ├──pageA.css # pageA CSS 代码
复制代码
看目录结构清晰明了,由于“ CSS 文件分离 != CSS 作用域隔离”这样的机制,如果我们不通过一些工具或规范来解决 CSS 的作用域污染问题,会产生非预期的页面样式渲染结果。
假设我们在组件 A 和组件 B import 引入 comA.css 和 comB.css。
comA.css
comB.css
.title { font-size: 14px;}
复制代码
最后打包出来的结果为:
.title { color: red;}.title { font-size: 14px;}
复制代码
我们希望,comA.css 两者互不影响,可以发现,虽然 A、B 两个组件分别只引用了自己的 CSS 文件,但是 CSS 并没有隔离,两个 CSS 文件是相互影响的!
随着 SPA 的流行,JS 可以组件化,按需加载(路由按需加载、组件的 CSS 和 JS 都按需加载),这种情况下 CSS 作用域污染的问题被放大,CSS 被按需加载后由于 CSS 全局污染的问题,在加载出其他一部分代码后,可能导致现有的页面上会出现诡异的样式变动。这样的问题加大了发布的风险以及 debugger 的成本。
小编我从写 Vue 到写 React , Vue 的 scoped 完美的解决了 CSS 的作用域问题,那么 React 如何解决 CSS 的作用域问题呢?
解决 React 的 CSS 作用域污染方案:
方案一:namespaces
方案二:CSS in JS
方案三:CSS Modules
方案一:namespaces
利用约定好的命名来隔离 CSS 的作用域
comA.css
.comA.title { color: red;}.comA .……{ ……}
复制代码
comB.css
.comB.title { font-size: 14px;}.comB .……{ ……}
复制代码
嗯,用 CSS 写命名空间写起来貌似有点累。
没事我们有 CSS 预处理器,利用 less、sass、stylus 等预处理器,代码依然简洁。
A.less
.comA { .title { color: red; } .…… { …… }}
复制代码
B.less
.comB { .title { font-size: 14px; } .…… { …… }}
复制代码
貌似很完美解决了 CSS 的作用域问题,但是问题来了,假设 AB 组件是嵌套组件。
那么最后的渲染 DOM 结构为:
<div class="comA"> <h1 class="title">组件A的title</h1> <div class="comB"> <h1 class="title">组件组件的title</h1> </div></div>
复制代码
comA 的样式又成功作用在了组件 B 上。
没关系,还有解,所有的 class 名以命名空间为前缀。
<div class="comA"> <h1 class="comA__title">组件A的title</h1> <div class="comB"> <h1 class="comB__title">组件组件的title</h1> </div></div>
复制代码
A.less
.comA { &__title { color: red; }}
复制代码
B.less
.comB { &__title { font-size: 14px; }}
复制代码
如果,我们的样式还遵循 BEM (Block, Element, Modifier) 规范,那么,样式名简直不要太长!但是问题确实也解决了,但约定毕竟是约定,靠约定和自觉来解决问题毕竟不是好方法,在多人维护的业务代码中这种约定来解决 CSS 污染问题也变得很难。
方案二:CSS in JS
使用 JS 语言写 CSS,也是 React 官方有推荐的一种方式。
从 React 文档进入 https://github.com/MicheleBertoli/css-in-js ,可以发现目前的 CSS in JS 的第三方库有 60 余种。
看两个比较大众的库:
reactCSS
styled-components
reactCSS
支持 React、Redux、React Native、autoprefixed、Hover、伪元素和媒体查询(http://reactcss.com/)
看下官网文档 :
const styles = reactCSS({ 'default': { card: { background: '#fff', boxShadow: '0 2px 4px rgba(0,0,0,.15)', }, }, 'zIndex-2': { card: { boxShadow: '0 4px 8px rgba(0,0,0,.15)', }, },}, { 'zIndex-2': props.zIndex === 2,})
复制代码
class Component extends React.Component { render() { const styles = reactCSS({ 'default': { card: { background: '#fff', boxShadow: '0 2px 4px rgba(0,0,0,.15)', }, title: { fontSize: '2.8rem', color: this.props.color, }, }, }) return ( <div style={ styles.card }> <div style={ styles.title }> { this.props.title } </div> { this.props.children } </div> ) }}
复制代码
可以看出,CSS 都转化成了 JS 的写法,虽然没有学习成本,但是这种转变还是有一丝不适。
styled-components
styled-components,目前社区里最受欢迎的一款 CSS in JS 方案(https://www.styled-components.com/)
const Button = styled.a` /* This renders the buttons above... Edit me! */ display: inline-block; border-radius: 3px; padding: 0.5rem 0; margin: 0.5rem 1rem; width: 11rem; background: transparent; color: white; border: 2px solid white; /* The GitHub button is a primary button * edit this to target it specifically! */ ${props => props.primary && css` background: white; color: palevioletred; `}`render( <div> <Button href="https://github.com/styled-components/styled-components" target="_blank" rel="noopener" primary > GitHub </Button> <Button as={Link} href="/docs" prefetch> Documentation </Button> </div>)
复制代码
方案三:CSS Modules
利用 webpack 等构建工具使 class 作用域为局部。
CSS 依然是还是 CSS,例如 webpack,配置 css-loader 的 options modules: true。
module.exports = { module: { rules: [ { test: /\.css$/, loader: 'css-loader', options: { modules: true, }, }, ], },};
复制代码
modules 更具体的配置项参考:https://www.npmjs.com/package/css-loader
loader 会用唯一的标识符 (identifier) 来替换局部选择器。所选择的唯一标识符以模块形式暴露出去。
示例: webpack css-loader options
options: { ..., modules: { mode: 'local', // 样式名规则配置 localIdentName: '[name]__[local]--[hash:base64:5]', },},...
复制代码
App.js
...import styles from"./App.css";...<div> <header className={styles["header__wrapper"]}> <h1 className={styles["title"]}>标题</h1> <div className={styles["sub-title"]}>描述</div> </header></div>
复制代码
App.css
.header__wrapper { text-align: center;}
.title { color: gray; font-size: 34px; font-weight: bold;}
.sub-title { color: green; font-size: 16px;}
复制代码
编译后端的 CSS,classname 增加了 hash 值。
.App__header__wrapper--TW7BP { text-align: center;}
.App__title--2qYnk { color: gray; font-size: 34px; font-weight: bold;}
.App__sub-title--3k88A { color: green; font-size: 16px;}
复制代码
总结
(1)如果是 ui 组件库中使用
建议使用 namespaces 方案
原因:
(2)如果是业务代码/业务组件中使用
CSS in JS / CSS Modules
业务代码维护人员较多且不固定、代码水平不一致,只通过规范来约束不靠谱,无法保证开发人员严格遵守规范,不能根治 CSS 交叉影响问题,但是从 debug 角度考虑,建议组件外层都添加一个 namespaces 方面定位组件。然后加之 CSS in JS 或 CSS Modules 方案来解决 CSS 交叉影响问题。
CSS in JS 和 CSS Modules 谁优谁胜?
CSS Modules 会比 CSS in JS 的侵入性更小,CSS in JS 可以和 JS 共享变量,但个人更喜欢 CSS Modules ,但是谁优谁胜无法武断。
头图:Unsplash
作者:七喜
原文:https://mp.weixin.qq.com/s/c0zbwrqDQOAhwZulNV4Dtw
原文:如何在 React 中优雅的写 CSS
来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]
转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论 2 条评论