Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 组件中如何组织 CSS #13

Open
ustccjw opened this issue Nov 10, 2015 · 3 comments
Open

React 组件中如何组织 CSS #13

ustccjw opened this issue Nov 10, 2015 · 3 comments
Labels

Comments

@ustccjw
Copy link
Owner

ustccjw commented Nov 10, 2015

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

@ustccjw ustccjw added the blog label Nov 10, 2015
@rockcoder23
Copy link

Web 组件化的价值在于分治而不在于复用。我认为这个需要对组件做更细致的区分才能做出论断。对于基础组件,在于复用;对于业务组件,在于分治。由于基础组件复用性更强,我们可能需要更细致的去设计和实现。

很赞同这种想法,我理解的楼主说的基础组件是只常见的UI组件(比如modal, panel等)。但是除了UI组件 & 业务组件还有一类组件如:操作cookie, promise, ajax等组件要怎么处理呢?

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

其实可以在css-loader上传参数避免这种问题。如:css-loader?modules&importLoaders=1&localIdentName=[name]_[local]_[hash:base64:5]。但可能这种做法没有考虑到标准的兼容。

@ustccjw
Copy link
Owner Author

ustccjw commented Jan 23, 2016

@rockcoder23 我是觉得组件的规模尽可能小,基于结构就可以控制样式。

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

在实践中,我发现这样做的好处是让你能够自觉地拆分组件,而且生成的 DOM 结构更加语义化。

@alcat2008
Copy link

之前也曾对该方面进行过探索,试图总结一套完美的组件化方案。

@ustccjw 文章写的非常赞,总结的非常到位!

CSS in JS 方式有一点我感觉比较麻烦的就是,没法用 autoprefixer 类似的插件,兼容性处理较弱,这点是硬伤。

剩下的局部化方案就是 css-module 了,但在编码过程中不难发现,此种方式下的复用不是很方便,所以最终采用了折衷的办法,具体可参考 style 模块化思考。简单的说:需要复用的样式直接 import,而组件或页面级别的样式则通过 css-module 构建。

目前方案存在的问题:1)所有的样式都会打包到 css 文件中,与按需加载的理念相悖,antd 提供的 babel-plugin-import 能从某种层面上解决组件层次的样式冗余问题,但对整个项目工程而言仅供参考。 2)外部想覆盖局部样式时的方式比较hack,舒适度不够。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants