Skip to content

React 组件中如何组织 CSS #13

Description

@ustccjw

React 组件中如何组织 CSS

组件和模块

这部分主要参照 hax 的 关于前端开发中“模块”和“组件”概念的思考 一文。
在 React 开发中,webpack 是模块加载和打包的利器,基于 webpack 的工作流已经非常完善 。Webpack 使用 JS Module Loader 来加载其他 JS 模块,CSS 依赖以及图片等其他资源。但是,这里只是指明了组件中相关的 CSS 依赖,并没有解决组件化与 CSS 样式全局有效的冲突。

基础组件和业务组件

以前在组件化的讨论中,@fouber@xufei 不止一次的说,Web 组件化的价值在于分治而不在于复用。我认为这个需要对组件做更细致的区分才能做出论断。对于基础组件,在于复用;对于业务组件,在于分治。由于基础组件复用性更强,我们可能需要更细致的去设计和实现。常见的 React 基础组件库有:material-ui, ant-design, react-toolbox。从实现来看,最大的区别就是如何组织组件的 CSS,以实现组件 CSS 局域化:

  • material-ui 使用的 CSS in JS 方案,在组件内使用内联样式;
  • ant-design 给组件取一个特殊的 className,以保证组件的 className 唯一;
  • react-toolbox 使用 css-modules,通过 CSS 文件的路径或者 base64 编码来生成唯一的 className。

CSS in JS

CSS in JS 通过 DOM 的 style 属性来实现 CSS 在组件上的挂载,并且保证了组件的封装性和隔离性。不过,这尼玛是内联样式,不是花了很长时间才把着玩意干掉的吗?这样做是不是违背了结构与样式分离的最佳实践(实际上,JSX 好像也违背了结构与行为的分离)?

Web 发展初期,为什么我们没有分离结构,样式和行为?为什么当时想不到耦合的问题?因为初期 web 页面是局限于很简单的结构,你甚至可以理解为一个页面就是一个组件。由于结构简单,样式和行为基本很容易控制,分离结构,样式和行为显得没有必要,因为实际运行的页面是结构,样式和行为的叠加。

随着 web 页面结构开始变得庞大,样式变得酷炫,交互变得复杂,我们发现内联样式和行为使得代码的可维护性变得很差,于是我们通过『选择器』来进行解耦,样式和行为都通过选择器来和结构挂钩。

Web 发展到现在,早已不局限于简单的 web 页面。Web 应用正大行其道,各种 MV* 框架应接不暇, JS 模块化和 web 组件化早已不是新鲜事。Web 应用一般都是一个 SPA,SPA 的一个典型特征就是部分加载,组件化也就显得很自然。组件蕴含着封装和自治:JS 的模块化已经非常成熟,CSS 并没有类似的模块化机制,我们需要 CSS 模块化或者局域化。实际上我们将解耦的目标从结构、样式和行为(通过选择器)转变为组件间(通过组件属性 props)。组件化开发下,由于层层组合嵌套,单个组件内部实现就会比较简单,组件内聚合反而更好。这样就不难理解 React 在 HTML 中直接绑定事件处理器了,甚至提出了 CSS in JS

CSS Modules

Css-modules 是通过工程化的方法自动生成唯一的 className,以实现 CSS 局域化的初衷,但是这样实现的侵入性太大,而且会造成 class dirty,而且自动生成的 className 与 HTML class 语义相违背。

类似方案如:ant-design 是手动给组件内所有的 className 加一个唯一的组件前缀来实现局域化。

理想的方案

CSS in JS 的主要缺点有:内联样式不支持一些伪类/伪元素/media query 等;内联样式书写起来比较困难。

Css-modules 和给组件内部 className 添加前缀主要的缺点在于:class dirty;不能保证 CSS 绝对局域化。

理想的方案是:使用 style 元素的 scoped 属性(很遗憾,目前只是 LS 阶段)。我们可以使用预处理器(sass/postcss)来实现一些模块化抽象(函数,mixin 等),使用 scoped style 来实现 CSS 局域化(可以利用 webpack 将依赖的的 CSS 插入到组件的根节点,并添加 scoped 属性,比如叫 scoped-style-loader)。

考虑到兼容未来的 scoped style,现阶段,我们可以这样组织组件 CSS:

组件的根节点使用 custom tag(唯一标识组件),内部样式使用标签结构选择器来定制(不使用 className),外面包一层根节点 tag(用来保证 CSS 局域化)。

这样看起来和 ant-design 的做法类似,但是我们『使用 custom tag 而不是 className 来唯一标识组件』,并添加了『内部样式使用标签结构选择器』这一限制:

  • 保证语义化——组件 tag 比 className 更符合语义;
  • 控制 class dirty——组件内部实现无需语义化,不需要使用 className;
  • 方便自定义样式——默认样式只通过标签结构选择器,优先级低,方便自定义覆盖;
  • 用语义来衡量组件拆分粒度——如果你觉得组件内部只依靠标签结构选择器无法很好的控制样式,那么很可能是组件内部需要进一步语义化,可以考虑进一步细化组件。

实验可参考:https://github.com/ustccjw/tech-blog

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions