我们生活在一个新的时代,每一天都充满了各种各样的新工具和范式。我们总是试图将旧有的架构应用在新技术上,而那样极可能以失败告终。 其中的一个例子便是 BEM—— 一个 CSS 命名约定,它解决的是那些可能不会再次出现的问题。
先来讲一讲重要的背景知识。
BEM**** 是什么?
BEM 是 CSS 的一个命名约定,遵从简单而直接的哲学:代码的一致性、伸缩性和可重用性。这个方法论正是源于它的名字:Block-Element-Modifier。这意味着所有的类会被拆分成三个实体,每一个实体在架构和代码组织上都具有不同的目的和角色。
- 块(Block)****: 具备独立逻辑和功能的组件。
- 元素(Element)****: 块中没有独立意义的部分。
- 修改器(Modifier)****: 定义块或元素的行为和外观。
当讨论遵从 BEM 规范的选择器时,有以下三条规则:
- 只使用 CSS 类选择器。
- 类名可以包含数字但不应该包含特殊字符。
- 使用连字符来分隔单词。
BEM 实体应该是这样的:
/* 块的名字,也是块中元素和修改器的命名空间 */ .block /* 块的命名空间后面紧跟下划线和元素名字 */ .block__element /* 块或者元素的命名空间后紧跟连字符和修改器的名字 */ .block--modifier, .block__element--modifier
为什么要使用BEM?
BEM 哲学的提出基于以下几个前提:代码的一致性、代码的伸缩性、代码的可重用性、生产力和团队协作。从 BEM 的哲学出发,不得不说 BEM 是非常好的范例,并且已经在全球范围内被来自大公司(如 Google 和 Twitter)的许多开发者所采用。
如果想要了解关于这个方法论更多的信息,可以阅读这篇文章: CSS Architectures 。
让我们开始吧
当谈到代码的命名规范和代码结构时,不得不提到 BEM。BEM 很简单并且功能完善。然而,当谈论到 CSS 架构时还有另外一点必须考虑,那就是目录结构。于是我们不得不提到另一种架构:ITCSS。
ITCSS 是由 Harry Roberts 提出的一个方法论,用于创建、管理和衡量大型的 CSS 项目。他曾说:
ITCSS__ 是一种方法论,旨在将整个 __CSS__ 项目可视化为一个分层的倒置三角形。这种分层架构代表了一个模型,可以帮助你以最有效、最节省资源的方式使用 __CSS_。_
本文不会深入探讨这个方法论,不过我们推荐阅读 Harry 的文章以掌握其背后蕴含的概念。简而言之,ITCSS 主张以分层的方式组织代码,以倒三角的方式组织结构,这样我们就可以在顶层定义通用元素,在底部定义特定元素。
这里的关键在于我们可以使用分层的方式作为一种有效的目录结构来组织代码。在我之前的经验中,我所使用的方法与其提出的原始目的有一点不同,我会用以下的方式对代码进行分层:
- Settings: 基本配置和变量。
- Tools: 函数和 mixins。
- Base: 应用到每一个页面的基本样式。在这里放置 reset/normalize 的代码最好不过了。
- Components: 组成界面的 UI 组件。
- Utilities: 非常具体的规则,这是唯一可以使用!important 的层。
现在我们需要做的事情是混合这两个方法,然后一个强大且简单的 CSS 架构便唾手可得。
ReactJS**** 的方式
在过去几年中,BEM+ITCSS架构满足了我们所有的需求,直到一切都开始发生变化。当 ReactJS 成为主要的单页面应用库,以组件化进行思考的方式较以前便有些不同。现在,每一个组件是一个 JS 模块,并且每个模块中的 HTML 结构完全关联该组件,并且通过自定义属性可以动态变更组件状态和变量。不再需要将样式标签与语义化的 HTML 结构关联起来,现在你可以创建完全逻辑化的动态功能性组件。
所以在新的场景下我们怎样处理样式和代码结构呢?保持样式和 React 组件相分离,使用 BEM+ITCSS 方法,或者彻底改为使用CSS-in-JS方式?我会建议介于这两者之间,并且利用我们之前所积累的知识。
Think-Adapt-First**** 方式
使用这些方法论和命名约定的想法源于以下原因:保持样式隔离、创建具有唯一性的选择器并在组件作用域内理清选择器之间的关系。人们使用BEM约定并不是因为他们喜欢使用双下划线或双连字符,而是因为 BEM 有助于提升代码的一致性、伸缩性、可重用性、生产力和可预测性。这些都是可行性的前提,所以为什么不一直用它呢?
因为我们不能。当我们决定在项目中引入React时架构发生了改变。于是,一些限制、模式和新的可能性出现了。这是将我们以前的规则应用于新环境的最佳时机。下面是我强烈推荐给你的工具。
CSS**** 模块
为了说明 CSS 模块的所有优势和功能,我想引用其作者 Glen Maddern 的话。除了它能够带来成堆的好处,最主要的就是CSS**** 本地作用域。现今,所有的 React 组件都可以在逻辑和呈现状态上进行完全的隔离。下面是代码结构的直观显示:
在 style.css 中不再需要保持经典的 BEM 约定。依据 BEM 约定,我们应该将第一个实体作为组件的命名空间,但是现在有了 CSS 模块,这种关系会基于 JS 组件名进行动态的创建。
/* * Reset */ .button { appearance: none; border: 0; background-color: white; cursor: pointer; color: #333; font-size: .9rem; font-weight: bold; } .button:focus, .button:focus:hover { border-color: #51a7e8; box-shadow: 0 0 5px rgba(81, 167, 232, .5); outline: 0; } /* * Sizes */ .small { padding: .5em 1.4em; } .medium { padding: .8em 1.4em; } .large { padding: 1.2em 2.2em; } /* * Themes */ .default { border: 1px solid #ddd; border-radius: 3px; background: linear-gradient(to bottom, #fefefe 0%, #ddd 100%); } .default:hover { border-color: #bbb; background: #ddd; } .default:active { border-color: #aaa; background: linear-gradient(to bottom, #bbb 0%, #ddd 44%); }
按照规则,你的组件中应只包含需要的类。我们可以通过下面的方式来描述一个按钮:
import PropTypes from 'prop-types' import React from 'react' import styles from './styles.css' export const ButtonType = { BUTTON: 'button', RESET: 'reset', SUBMIT: 'submit', } export const ButtonTheme = { DEFAULT: 'default', POSITIVE: 'positive', DANGER: 'danger', } export const ButtonSize = { SMALL: 'small', MEDIUM: 'medium', LARGE: 'large', } const Button = ({ type, onClick, children, theme, size }) => { const className = `${styles.button} ${styles[theme]} ${styles[size]}` return ({children}) } Button.propTypes = { type: PropTypes.oneOf(Object.keys(ButtonType)), theme: PropTypes.oneOf(Object.keys(ButtonTheme)), size: PropTypes.oneOf(Object.keys(ButtonSize)), onClick: PropTypes.func.isRequired, children: PropTypes.node.isRequired,} Button.defaultProps = { type: ButtonType.BUTTON, theme: ButtonTheme.DEFAULT, size: ButtonSize.MEDIUM, } export default Button
好的,下面我们给出组件分层的解决方案。那么如何定义全局样式呢,比如设置、规范化和重置?
ITCSS**** 复活
既然基于 ITCSS 的分层目录结构仍然可以完美匹配我们的需求,那么为什么不继续使用它呢?
上面的想法是在src**** 目录中创建与 ITCSS 同风格的样式文件目录,除了组件的文件目录外,因为 React 已经默认定义了组件的文件目录结构。
现在,我们会得到下面的目录结构:
我们可以使用:global标签在主应用程序中导入样式文件,样式可以作为全局的类来使用。
再应用Think-Adapt-First方式
该结构背后的理念是通过以一种可伸缩的方式保持CSS**** 架构创建更好的 ReactJS 项目,可以支持成千上万的组件和开发人员协同工作。
然而本文的真正关键点在于打开你的思维,去适应新事物!有时候适应是很困难的,因为对于难以掌控的事物,你必须放手,但是请不要忘记,你已经解决的问题可能将以一去不复返。
所以,年轻的朝圣者们,请持续探索知识。
查看英文原文: CSS Architecture with ReactJS
感谢薛命灯对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论