BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

ReactJS中的CSS架构

| 作者 Douglas Gimli 关注 0 他的粉丝 ,译者 猫儿不熊 关注 0 他的粉丝 发布于 2017年8月16日. 估计阅读时间: 13 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

我们生活在一个新的时代,每一天都充满了各种各样的新工具和范式。我们总是试图将旧有的架构应用在新技术上,而那样极可能以失败告终。 其中的一个例子便是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)关注我们。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT