Skip to content

React 组件中如何组织 CSS #13

@ustccjw

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions