如何看待《React: CSS in JS》?
感谢 @liusong1111 在 https://github.com/hax/hax.github.com/issues/21 话题讨论中提供facebook的这个slide:React: CSS in JS。
slide的前半部分,我没想到facebook能在错误的工程化道路上走那么远(这个可以理解为赞美)。我在前文中“局域化CSS”一节写道:“覆写和扩展class钩子或可类比为某种接口使用,但实际运行时并没有任何约束,我们也很难进行静态检查”。但是facebook居然做到了显式接口和静态分析。这完全来自于f家强大的工程能力,体现了f家前端架构师的牛X。不过可惜的是,即使做到这个地步,这条路仍然是走不通的,他们自己最后也意识到了。BTW,这也基本上可以视作对OOCSS、SMACSS、BEM等方法论宣判的死缓。
后半部分是他们的一个解决方法。
包括:
- 使用inline style
- 使用JS表达样式组合,从而利用所有JS的特性(如模块化)
- 放弃使用selector的specificity机制,而是在插入inline style时使用JS的调用顺序来明确override样式的优先级(在这个点上有极高的灵活性,是完胜传统class钩子的关键点)
老实说,我一点也不意外这样的方案,我甚至惊讶于从Bootstrap流行开始花了这么久才走到这一步。实际上我一直认为“样式为中心”的开发方式最终应该回归到inline style上。
但是,这样的方案是不是胜过了我一直推荐的CSS preprocessor的方案?我认为没有。
此slide列出的CSS的scale问题(即大规模开发时的可维护性问题,或者通称“工程化问题”)如下:
- Global Namespace
- Dependencies
- Dead Code Elimination
- Minification
- Sharing Constants
- Non-deterministic Resolution
- Isolation
(待续...)
「体现了f家前端架构师的」后面漏了内容。
@Justineo 已补上。当时想写什么词好,一时没想出来就漏了。
最近做毕设也有这方面的疑问,我都是直接给组件传class,这也有时会导致父组件样式牵连到子组件的样式i~~做完后感觉React组件化没带来什么代码量的减少,维护性也没怎么提高。也许我接触较浅吧,才几天。还有一点,感觉React做动画有点不适应,因为所有UI变化都通过sate值来触发,为的就是利用那个diff(还没具体了解这玩意)算法吧。
早上想到一个可能性就是解析css模块为inline-style,跑来一看居然有了
由此看来,react的野心很大。jsx囊括了js和html部分,再加上css,那么一个纯粹的html组件名副其实:该有的都有,别人引用的话也是引用一个组件对象,而且这个组件样式、结构、逻辑交互全部内聚了起来,通过暴露接口和外部通信。
果然是个强悍的思路。
@army8735
有很多方案解决组件内聚问题,react不是唯一,更不是首创。
组件化方案有两大类,一类是纯前端方案,以模块化框架为依托,把css、html导入到js中使用,然后前端通过模块框架管理js实现全站的组件化方案:
纯前端组件化方案
1. 一个模块的代码组织在一个目录下(就近维护)

2. 将模板(HTML片段)嵌入到JS中使用

这里需要构建工具提供一种资源嵌入的语法
3. 针对HTML片段写局域化样式

CSS也可以建立自己的依赖
4. 就近引用资源

5. 页面引用一个模块就引用了模块的所有内容

这就实现了纯前端的组件内聚,模块依赖和资源管理的方案。
对于后端渲染,也有类似的技术方案,这里就不展开了,要说的比较多,细节可以看我的博客: https://github.com/fouber/blog/issues/3
require.aysnc('#header')发生了什么事?
另外有2点疑惑,其实也是类似webcomponents的东西: 1.组建递归和通信如何设计? 2.shadowdom中的css如何实现?html不可能实现了。
@army8735
在require.async之前,还有一句代码,就是:
require.config(__FRAMEWORK_CONFIG__);
require.async('header')之所以能加载整个组件是因为:
1. 页面中的 __FRAMEWORK_CONFIG__ 变量会被构建工具替换成一组数据

2. 这组数据其实是工具扫描整个项目模块化js、css而建立的依赖信息

3. 这样,require.async加载一个组件,就能知道这个组件依赖的所有资源,然后把它们都请求回来:

这个请求的过程还能根据需要做本地缓存或者合并加载。
在这样的模块化基础上建立组件化会更加轻松,组件化可选的方案就非常多了,因为主要是利用组件化解决组件封装问题。而资源的内聚和加载由模块化框架管理,一个负责上层封装,一个负责底层资源,这种方案也合理吧。
1.组建递归和通信如何设计?
2.shadowdom中的css如何实现?html不可能实现了。
这两点呢? 加载了css资源,自然造成了污染,而不像web组件一样隔离。 而组建的自定义标签引入、属性配置和嵌套等?
@army8735
1. 组件组合和通信的机制,取决于业务。
在实现了模块之后,怎么封装组件就有很多可选方案了,可以用传统模式,基于DOM操作,然后设计一个消息中心:

当然,这样很传统,也很繁琐。由于我们的应用是移动端,所以选择了VUE这个MVVM框架,轻量,可以用于生产环境。解决了组件封装问题,基于数据驱动,也就不用“通信机制”了:

同样,也尝试了React的项目:

其实模块资源管理和组件化框架并不冲突,二者结合会更显优势。
2. CSS样式的全局性问题
我承认,css样式是注入全局的,所以有风险,但当你在工程中实现了组件的分治之后(一个组件一个模块,一个模块一个目录),css只是针对组件写样式,只要稍加约定,出现干扰的问题已经大大减少了,目前这些措施无论在我上一家公司还是现在这家,都实践的很好,尤其是引入预编译css之后。比如我们的一个视频组件
@import "../../views/lib/stylus/mixins/css3"
.funny-video
box-sizing border-box
padding-right 3px
float left
&:nth-child(2n)
padding-left 3px
padding-right 0
.funny-video__link
display block
position relative
min-height 45px
margin 0 auto
>img
// 290*180
width 142px
height 88px
.ico-play-btn
playBtn 28px, #fff, #fff, 'transparent', 2px
display block
position absolute
right 11px
bottom 11px
不过shadow dom中的css方案确实很好,但就真的是我们期待的么,可以工程上很方便的实践同时与性能优化、资源加载、本地缓存、请求合并等等问题和平共处么?我对此持观望态度。以上我说讲的方案,都是经过工程上规模化实践的结论,webcomponents方面估计还要再等等,实践过了我会来补充的。不过现在的方案已经用的比较舒服了。
内联css的方式和性能上没啥联系吧。
如果通过解析手段将css注入到jsx的内联中,那么组件一词将名副其实:完全独立的静态资源,然后被引入,可以做缓存、合并,还是天生shadow的,除了html。而由于html部分没有class、id等,其实基本上也就shadow了。 而在调试开发阶段,这些均可做映射到源代码,又实现了调试的需求。所以上面我才会说,解析到内联上是一个思路。
@army8735
恩,是一个思路,这种设计非常漂亮,但也非常激进。所以回到问题的开头,我想说react并非唯一选择,而且我觉得在解决组件分治和组件资源加载问题之后,前端开发模式就非常多了,前面介绍了好几种基于相同模块化方案的组件化设计,shadow问题我觉得在分治的大前提下就没有很大期待了
另外我感觉非常好的一个特性就是静态资源化。可以变成一个文件被引用,如此实现复用。
传统上组建依赖tpl、js和css,tpl和js可以被合并,但css却合并不进来,作为被依赖存在时,永远需要加载个css这个逻辑。
如果单一静态资源化,那么就能像npm那样,甚至未来的es6 module那样,直接import……
也许CSS的模块化不必拘泥于树形组织结构, CSS天生的世界观就是一维的,全局的,也许我们应该保持它这个天性,而不是努力统一到JS和DOM的世界观下,虽然他们总是在一起。
把它当做和业务模块(JS+DOM)正交的一个维度。毕竟CSS设计的目标是样式分离于内容,我们应该可以用同一个业务模块搭配不同的CSS就得到完全不同的表现——按这个思路,CSS压根就不 属于 业务模块呢
业务模块是人的话,CSS就是他们的制服。餐厅服务员和酒店服务员的技能类似,换身制服就可以跳槽,把CSS集成进业务模块的努力,是不是有点像把制服彩绘到人身上的赶脚?
@amio
分而治之是重要的编程思想,我们对css这种具有“全局”性质的代码单元更应该采用这种策略。样式必然有跟组件结合在一起的情况,因为组件本身就需要有针对自己展现的描述。
我个人比较反对用非技术概念来类比技术概念,因为可以从很多角度解读。按照你的类比,组件样式可以理解为人的生活外套、内衣、眼镜什么的属性,你自己得穿的,然后在不同的场合可能由场合提供了某些“制服”。
正式的开发概念就是:组件本身有自己的样式,但组件在被其他组件组合使用的时候,父组件可以通过复写子组件的部分样式(比如宽高、显隐等)使之适应当前组件内的展现需求。就好像餐馆为你提供了制服,但你来上班总得穿着一套过来,上班下班、入职跳槽总不会都是裸体吧?
这点可以参考其他GUI软件的设计,前端只不过是一种稍微有点特殊的GUI软件而已
我倒不是说目前的方式不对,只是探索也许有另外一条路可以尝试。
在这条纵容CSS独立地位的道路上,组件本身的基础样式也是附加的,而不是融合在组件内;它支持彻底的换肤,而非复写样式以适配所处环境。这条道路上的人信仰(每个宗教都有一些极端分子)HTML 也是数据和结构,CSS则是表现,这两部分是成套提供的,但也可以扔掉CSS只要数据,某些人就是喜欢外带开封菜去校园草坪上吃嘛(实在憋不住老想打比方……你就忽略好了,反正我没有试图完全映射两种概念,只是打比方更多是吐槽这么一个具体的点而已)。
恩,也许可以这么概述这条路上的人的信仰:
HTML 是内容,CSS 是表现。它们各搞各的模块化,但就是不会搞成同一个模块化。
@amio
恩,这个信仰也很不错,而我的信仰是这样的:

@hax
但是,这样的方案是不是胜过了我一直推荐的CSS preprocessor的方案?我认为没有。
大神推荐的CSS preprocessor的方案哪里有介绍?给个link呗?多谢
@mooncakelmn 请参考 http://efe.baidu.com/blog/revisiting-css-preprocessors/
@hax
多谢大神的快速响应,这篇文章比较的非常细致,加深了我对这三大preprocessor的理解。
但是依然没能解决我的问题。这种方案还是在开发阶段保证了模块化(组件化)。一旦编译之后到生产环境下,还是会变成全局的CSS,依然避免不了组件被污染。但是React用inline style的方式,就大大降低了被污染的可能。
@mooncakelmn 预处理器方案可以将可复用的ruleset混入到指定的语义结构中(比如以id限定),所以不存在污染的情况。
我说个跟主旨无关的问题吧。
我很好奇,如果用内联样式来渲染 UI,如何实现 CSS 中伪类和伪元素的功能?
@cssmagic 给节点自动生成一个guid作为class,然后插入一个style标签,style里面指定.guid::after什么的, 不过这样貌似就不是那种“内联”了
@cssmagic 从他们的这种架构来说这个问题不是很大,方法就是: A. 不用伪元素和伪类(因为大量用js控制,所以是可以不用的,比如after/before伪元素就直接在前后加container,结构性伪类因为直接inline了,所以许多是不需要的,少数逻辑可直接用数据绑定计算) B. @sapjax 讲的方式
@cssmagic Radium 用DOM Event 实现了:hover :focus :active
migi用jaw也实现了: http://army8735.me/migijs/migi/demo/style.html http://army8735.me/migijs/migi/demo/pseudo.html http://army8735.me/migijs/migi/demo/attr.html
最近猜想,preprocessor的import只能应用本地文件,可否扩展到应用到网络上资源。。
对 React Component 里面 CSS 的管理方式一直比较困惑,也经常跟同事争论。
首先,我稍微偏向于在 Component 里面 require 单独的 CSS 文件,然后在 Component 的结构上面挂上 className,主要观点如下:
- 可以继续保留 CSS 的一些特性,比如:类的共用、复杂选择器(虽然 Component 一般用不到)、伪类各种。
- 可以使用预处理工具,用 WebPack 打包。里面的 background 引入的图片资源以及其他 Web Font 等资源可以被 WebPack 解析处理。
- 现有插件(例如 Emmet)对 CSS 文件支持较好,编写效率高。
但是也有一些缺点,大约就是:
- CSS 属性是全局的不够封闭,如果有其他组件同名,会覆盖或者产生意外。
- 需要思考、规划 class 命名等。
- 与组件不够耦合,component 多了之后,管理维护可能比较麻烦。
- 属性值等不方便被 JS 处理。
而 Inline Style 的话,优点就如幻灯片里面那些,但是也有一些缺点:
- 代码会有一些冗余。比如:btna 和 btnb 有共同样式,按常规可以抽出一份 btn,但现在都要输出到 style 里面。
- CSS 要按照 JS 的写法来写,目前而言暂时没有很好的工具来支持,几乎只能靠手写,写起来并不是很幸福。
- CSS 的属性被 ' ' 包裹,直接输出到 style 标签中,WebPack 等无法对起资源进行扫描处理,甚至需要手动来部署 CDN,然后拿到 URL 引入。
- 查看结构的时候,看到的一大片乱七八糟的 inline style 和 div,缺乏 class,定位问题组件效率低。
感觉 Inline Style 的用法,直接否定了这几年 CSS 的发展和进步,直接回到了 CSS 最简单最基础的用法。各种选择器、继承、icon 等组件集中化管理等等,全都扔掉了。
然后但从 React 角度来看,这两种方案各有优缺点,两种混用会有点不伦不类,那应该如何取舍?hax 前辈对此有何看法?
写法应该还是传统写法,只是构建时解析inline而已。
另外原始的class和id,应该被转换为pre-class和pre-id用以定位及他用,比如外部需求覆盖。
.name{color:#FFF}
var div = <div class="name">name</div>;
<div pre-class="name" style="color:#FFF">name</div>