聊聊 React 组件库的技术选型与设计
{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最近在业务中开发了一套定制化的 C 端组件库,在这个过程中遇到了一些组件库技术选型和设计的问题,在参考公司内外的多个组件库后确定了最终的方案。本文希望通过向读者介绍技术选型的过程中的方案比较和组件库设计中的考量,让读者在组件库的技术选型和设计上有所启发。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f4\/f4fa8d8b2431f9a8d5d4e606e87a4c06.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个完整的组件库方案的思路"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"组件库的技术选型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"样式方案选择"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/24\/248c15374c57d407c3d87cfdab05e576.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事实上,这三种样式方案可以并存,但实际开发以其中一种为主。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Sass\/Less"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是大家最熟悉的方式,它的优点是足够灵活、开发成本低(绝大多数工程师都熟悉它们)、 完全支持外部覆盖组件的样式,缺点是难以调试(需要到 runtime 才能知道命中的规则),以及难以实现静态分析。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Atomic CSS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 UI 足够标准化的情况下,使用 Atomic CSS 能实现更小的包体积大小,对於单个组件,除了极少数无法抽象的样式以及自定义动画,不再需要声明其他样式。当然它的缺点是代码可读性稍稍降低。同时开发者需要先熟悉项目的原子样式,增加了一定的开发成本。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"CSS-in-JS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CSS-in-JS 指包括 styled-component、Emotion、JSS 等在内的,在运行时通过 js 生成 css 样式的第三方库。CSS-in-JS 这种方案的优点在于能有效解决“组件样式随着数据变化”的问题。但是,它的缺点在于为了支持从外部覆盖内部元素的样式,需要给内部元素加上 className,同时不支持 postcss,取而代之的是特定 CSS-in-JS 库自己的 plugin 生态,少部分库(如 emotion)没有支持 rem 的工具库。另外在做 SSR 和流式渲染时,都需要在 node 层增加提取样式逻辑,增加了开发成本和额外的开销。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"小结:在有成熟的 UI 规范的情况下,Atomic CSS 是一个不错的选择,其次,使用传统的 sass\/less 来编写样式也利于维护(大部分前端开发者都熟悉它),在选用 CSS-in-JS 方案时则要考虑团队的开发习惯和上手成本。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"icon 方案选择"}]},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/d1\/92\/d1c58f2b3244e0d37bb1acaa3bafa492.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在选择 icon 方案的时候,除了关注渲染质量,我们还应该关注它的灵活性,以便具有更好的适配能力。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"iconfont"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"iconfont 这种方案的优点在于兼容性最好,支持 IE6 及以上版本。但是,由于 iconfont 方案是将 icon 作为文本来使用,"},{"type":"text","marks":[{"type":"strong"}],"text":"在 webkit 内核的浏览器下由于对文字有抗锯齿,导致渲染失真"},{"type":"text","text":"。另外,由于将所有的 icon 打包成一个字体文件,不支持按需加载,包体积偏大。这样很容易导致"},{"type":"text","marks":[{"type":"strong"}],"text":"在加载完成 icon font 后页面的重刷新"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/d5\/3f\/d50b341260106b9ebee6d1982670ca3f.gif","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"base64 引入"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"base64 也是一种常用的方法,但是由于将 svg 作为背景图引入,只能控制它的大小,不能覆盖它的颜色,也更不能修改 svg 内部的元素,不够灵活。对于常常采用 MPA 结构的端内 h5,不利于 icon 在不同 SPA 之间复用。同时 base64 字符串的长度是 svg 文件(优化后)的 1.3 倍左右。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"React Component、SVGUseElement 和直接写入 svg 元素"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这三种方式本质上都是将 svg 作为 html 元素进行渲染,但具体的使用方式不同。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"svg 的基本能力的兼容性除了在 IE11 以下不支持动画和缩放,基本没问题,而 svg effect(主要是使用 transform、filter 等属性)在 android4.4 以上的支持良好。svg 的动画性能有瓶颈,幸运的是我们可以使用 css 动画来替代它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直接写入 SVG 元素的方式缺点在于"},{"type":"text","marks":[{"type":"strong"}],"text":"完全无法复用同一个 icon"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 SVGUseElement 的具体实现方式有使用元素、 元素和 SVG fragment identifiers 等方式,但总的来说,都是在顶部声明 svg 元素,在需要使用的地方使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.