一个好的组件应该是什么样的?

背景

19 年 6 月左右,我发布过一篇文章《Bit 初体验》 。在梳理这篇文章的过程中,我可以说深度体验了一把 bit 所提出的概念和做法,就像一颗种子种在我的脑海中,一开始我觉得这东西没什么。

我还记得我第一次与我的同事分享 bit 后,他说:

emm,虽然你讲了这么多,但是我觉得好像没有那么...有体感?

感觉没什么卵用?

啊,emm,既然你说了,就像你说的。我觉得我们现在如果引入 bit 会不会对我们的日常工作带来很多额外的工作量。

这种反应很正常,我是在 18 年初,就在 Vue 的官网见到过 bit ,当时我点进去大致浏览过一下。我当时的感受就是,没什么卵用,无非就是 " 前端垂直领域的 git "。对国内的支持情况还不咋地,连一篇像样的中文文档都找不到。

在我们的团队中一下子直接切换到 bit 的工作流,这确实不现实,在公司有那么多的基础建设都不知道 bit 这么个玩意。

但是,bit 的做法和概念,却是非常非常有价值和可以借鉴的!

所以,我想做一件事情,一步一步的把 bit 的玩法用我们熟悉的方式引入进来甚至有所延伸扩展,让大家认同其中的好处和价值。

认识组件

随着近些年”微前端“概念的不断酝酿,越来越多的团队开始着手将自己的业务处理为不同组件,然后通过一些微前端做法,编排到一个业务页面中去。

那么对于组件的维护就会变得越来越重要。所以,先来看看现在大多数团队是怎么维护组件的吧!

  • 大库型,Antd、Element 标准的大库型
  • 一次型,完全业务组件,用完一次再也不维护
  • 高复用型,一看就应该单独封装以后给其他人用,比如:视频播放器
  • 项目融合型,与业务项目在一起,混合 store,不分你我

我暂时能想到的就这几种类型的组件,如果你的团队也在维护自己的一套组件库,那么应该很容易理解我上面所说的。

我相信,既然这么做了,肯定有这么做的理由和好处,没有人会闲着没事找麻烦做不是,那么这些做法都有什么好处和痛点呢?我从几个方面入手分别分析一下。

方便、快捷

组件嘛,当然是最快能跑起来,最方便能看到效果最好咯。就这点来讲,还有什么比直接在业务项目里撸组件更快的方式吗!?

现在用个展示的面板,立马去 components 目录撸一个。

数据?不是有 store 吗?引入进来不就拿到数据了!

所见即所得,现在改完马上看到页面上的效果!无法反驳..

这么看确实开发这个组件是好快了,但是从整个业务需求实现来看,这么做真的是最快的吗?如果这样的做法是最快捷的,那为什么那么多团队在强调沉淀、封装、抽象呢?

其实很多组件当时看起来,这辈子就只可能用一次,不用封装。可是往往交互稿过来的时候就会发现,这个样式好像我在哪里见过。然后去各种业务项目里一顿翻,哇终于找到了,复制过来发现各种爆红,定睛一看,store???

所以,聪明的团队早已洞察这一切,让我们把组件都维护到同一个地方,然后大家写好文档,用的时候从库里面取就可以了,有 Bug 的话统一修复就是,棒 ????!

可维护性

于是乎,大家便如火如荼的开始的组件抽象,组件整改的浩大工程。

一开始,一般会有一个团队中较为靠谱、能力突出的小伙子(嗯?怎么是我?)去把 Webpack、Babel、TypeScript、SassLess、目录结构、单元测试结构、代码规范、Review 规范、发布规范这些梳理好,然后写一个标准的组件出来,最后再强调一下大家一定要按照规范认真维护组件,书写文档,编写单元测试。

从维护性上来讲,大家把组件都写在一个库里面,然后再用到的项目中直接引入,业务上的问题逐渐被分为组件问题还是项目问题,甚至有些需求可以用这个交互在组件库中有相似的,用那个组件就可以了,来反驳产品和设计 ????。

就在大家用的不亦乐乎的时候,有一天发现,呀,我们的组件库怎么打包出来有 10M 啊 ????!

然后找一个靠谱、能力突出的小伙子(没错又是我)就去查了下,这个库是谁引入的?这个组件不是已经有一个了吗?lodash 不是这么用的呀!这个组件是干什么的,怎么没文档?

面对上百个业务组件,只能感叹一声业务迭代的可真快啊。

所以,大库维护固然有大库维护的好处和适用场景,大家能够有这样的抽象思维已经是技术上的突破了,现在只是遇到了另外一个问题,解它!

组件大小、加载性能

接触 Webpack 的一些周边工具,比如 analyzer 很容易可找出具体是什么包”霸占“了这么多的流量。

发现原来组件包中还有一些个组件,看上去不应该放在大库中进行维护,比如那种一次性组件,二次封装型组件。

因为这种组件可能会引入一个很大的第三方依赖,比如视频播放器、Banner Swiper 等。

对于这样的组件,最好的处理方式应该是创建一个独立的仓库,封装完善后,写好 README,发布至内网 NPM,供业务项目使用。

But you know ,这样做成本太高,如果有时间的话,我肯定.....balabala...(一般来说,如果你对程序员说一个更好的方案时,除非这个方案他有参与设计,否则大部分回复都是这样 ????)

当然组件大小这方面也可以通过很多其他方式解决,比如:异步加载,NPM 引入,按需加载等等啦...那么,让我们谈谈下面另外一个很重要、又很容易被忽略的部分吧。

组件说明及可索引性

老板,我们今年沉淀了组件 200+,其中有几个组件写的特别好,同时支撑了 20+ 项目。

哇,这么棒!来给我看看你们写的组件长什么样?啊,这,这样来看看我们做的这个页面,这个页面里面用了这几个组件,balabala ...

设计:听说你们已经沉淀了 200+ 组件,能给我们看看有哪些组件吗?我们在下次设计的时候可以参考这些组件进行输出,减少沟通成本。

前端:@所有人 这个组件我们库里面有吗?有,CascadeSelect。哦,怎么用的?有文档吗?.......看下源码吧。well... ????

组件的说明及可索引性,其实仅次于组件的可用性,甚至更高。

试想下如果今天你写了个巨牛的组件,复用性、接口设计和交互设计都非常棒,但是你有什么渠道能让大家一下子就知道吗,难道你要专门为此拉大家开个会?来今天占用大家1个小时的宝贵时间,介绍下我今天写的巨牛组件。????

反过来想,如果我在写组件的时候,反正我这个组件也没啥亮点,别人应该也不会用到,就不用补充文档了吧,应该也没人会知道。哦豁,丸蛋 ????

索引组件,来给大家分享一张图:

一个好的组件应该是什么样的?

如果有一天你团队的组件库也能像这样,一板一眼有图有真相,那该是多么幸福和享受的一件事情!

我也知道这样好啊!谁不知道!如果我有时间,我肯定会....balabala...

所以你的意思是让我们每写一个组件不但要补充文档,还要补充用法说明,还要截图!?

对,还要单独建库,还要考虑配置 Webpack、Babel、TypeScript、SassLess、目录结构、单元测试结构、代码规范、Review 规范、发布规范这些 ????

最*实践

说这么多呢,主要是想带读者们一起思考,也是我写作的风格(喜欢讲故事),大部分内容其实是前端er 都会遇到的问题。

接下就进入正题,说了一大堆问题,总得有点办法来解决吧!

先看看 bit 是怎么做的吧,bit 首先自身有一定的编译能力,内置了 Webpack 及一些插件式 loader 来解决 React、Vue 等编译问题。

对于我们团队来说,都是使用 React,所以咱们就先从一个编译 React 的脚手架开始。

如果把每一个组件都作为单独的 NPM 项目发布,首先要考虑的是,前端一系列的编译环境。如果我有 N 个前端组件项目,每个前端组件库的 Webpack、babel 这些都需要重复配置,那真是要头大的事情,我只是想写一个组件而已,为什么要考虑这些。所以我们的脚手架首先要具备一些基础的编译命令。

啊对了,脚手架还没有名字,那就暂时叫它:comp 吧 ????

  • comp new:处理按照模板新建一个标准组件

    • 初始化一个标准组件项目结构,所有接入所有 comp 命令
    • 初始化 Git 仓库
    • 初始化 CI/CD 云构建配置
  • comp start:处理日常开发,附加单个组件展示及调试能力
  • comp watch:处理 babel 及 scss 监听编译,用于 npm link 场景
  • comp babel:处理编译 npm 包
  • comp dev:处理监听编译 umd 包,用于代理调试
  • comp build:处理最后编译过程

    • webpack 编译 UMD 包
    • Babel 包
    • CICD 过程中自动截图组件
    • CICD 过程中自动生成 README
    • 其他 Hook
  • comp test:处理 jest 单元测试

那么等组件初始化以后,目录结构就长这样:

一个好的组件应该是什么样的?

项目结构中没有任何 webpackbabel 配置,当然如果有特殊配置需求,可以建立 comp.config.js 进行配置(此处借鉴很多已有的 cli 处理方式)。

这样处理的好处是,在项目初始化后,用户能见到的目录结构非常清晰明了,项目中不有很多允许配置的地方,所有组件的编译环境基本可以保证统一。

这都是些非常基础的功能,当然又是不可缺少的部分,这些基础命令我就不详细介绍了,重点在后面。

通过这几个问题来介绍功能:

你平时开发组件的流程是什么样子?

平时,一般就是根据设计稿,切分到组件后。

然后去创建组件,最后通过项目引入,一边看着一边开发啊。

你开发组件的时候对于你提供的 Props 是如何验证的?

最简单的给一个 mock 看看效果呗。

或者写一个单元测试?

那写 Mock 的过程算不算是在写 Usage 呢?

这个,应该也算吧,但是这些都是散落在各个项目里面,有些 mock 验证完就删掉了。

谁会闲的没事在开发的时候把这些补充到 README 里面去啊。

为什么他们不写文档?

这还用说?因为懒呗?

那你为啥不写?emm,那是因为....写文档这事儿吧,写了不一定有人看,还费时间呀!业务需求那么多!我要是有时间的话,我肯定....balabala...

OK,那我们来看下一个问题

一个好的组件文档需要那几部分?

开发组件背景,注意事项啥的,这个没啥太大的必要,有的组件需要的话就补充下,没有的话就不用补充。

主要需要的一些介绍有 :用法,Props 入参,最好能有个截图,要是有个 Demo,那简直完美!

还有安装、开发、编译的命令介绍得有吧。

锦上添花的话最好还能有几个 badge,介绍下源码是 TypeScript,下载量多少。

但是,要补充这些文档是在太麻烦了,要一个一个整理,Props 这些信息,用的人可以在组件里面找到啊,我都有些注释和类型定义的呀!

完成一轮心灵拷问之后,就会发现在整个组件的开发过程中,开发者本人之所以对这个组件这么清楚,是因为开发者其实已经为自己写过一份 README 了。

  • 用法:组件开发过程中需要看到效果,写过一些 mock 数据,已经知道什么样的 props 传进去会产生什么样的效果。
  • Props 入参:组件有哪些 Props,所代表的含义是什么,每个 Props 入参类型是什么,已经在 TypeScript 的 Interface 及注释中有体现。
  • 截图:有 mock 数据还不知道长什么样?已经看过 N 多遍了。
  • Demo:根据用法和组件定义,开发调试的就是 Demo。

有了这四个最重要的介绍后,相信大部分开发者也都能知道这个组件是怎么个情况了。

所以,如果我们能把上面这些数据都收集到,是不是就可以利用脚本 自动生成 README 文档 了呢?

用法 / Usage

要收集用法其实很简单,如果让组件有独立开发的能力,不就可以保留这些 Usage 的 Mock 数据了吗?

有些人可能没理解我说的”组件独立开发的能力“是什么意思,我解释一下下:我们平时开发一个组件,一般都是把这个组件放置于某个页面中,一遍调试页面一遍调试组件。独立开发组件的意思是,直接启动一个页面,可以看到组件的样子,这个页面展示的就是围绕组件的所有信息。

所以在脚手架中,只要在 docs.ts 中书写需要调试组件相关的 mock 数据,页面就可以展示出组件的样子,同时这些 mock 数据可以保留作为 README 文档数据。

export default function(Component: typeof IComponent, mountNode) {
  /** DOCS_START 请将Demo生成方法都写在以下区块内,用于生成README及Riddle **/
  ReactDOM.render(
    <Component
      navigation={true}
      pagination={true}
      autoplay={true}
      dataSource={[
        {
          href: 'http://xxxxxxxx',
          image: 'https://xxxxxxx.cdn.com/tfs/TB1jHkBmNv1gK0jSZFFXXb0sXXa-1440-343.png',
        },
        {
          image: 'https://xxxxxxx.cdn.com/tfs/TB1Y_XacrY1gK0jSZTEXXXDQVXa-1416-813.png',
        },
      ]}
    />,
    mountNode,
  );
  /** DOCS_END **/
}

另外,如果保证这份 demo 的接口输出统一规范,还可以支持直接生成在 CodePen,Riddle 这些在线编辑的代码内容。

试想下,你的 README 中如果出现一段 :点击立即体验 ,跳转过去后可以在线编辑实时看到效果,那对于任何看到你组件的同学来说都是一种享受 ????

一个好的组件应该是什么样的?

组件参数 / Props

要收集这部分数据就比较复杂了,我们需要深入分析 TypeScript AST 语法树,提取出其中组件 props 的类型以及对于Interface的注释内容。

经过一番 github,终于找到可以实现一个可以处理这件事情的小众库:react-docgen-typescript(https://github.com/styleguidist/react-docgen-typescript)。

在开发过程中,因为对一些注释及类型输出与我预期的不太一样,所以我 fork 后做了一些修改,已经可以完成对一个完整组件的 Props 做分析后输出一份 typefile.json 。

一个好的组件应该是什么样的?

同样的,通过基于该能力,可以扩展为 webpack 插件 react-docgen-typescript-loader(https://github.com/strothj/react-docgen-typescript-loader),为组件的静态属性中添加 __docInfo 属性,来声明其属性内容,于是组件开发过程变可以实现以下效果:

一个好的组件应该是什么样的?

截图 / Preview

有了组件,有了 Demo,还愁没有截图吗?

直接在构建过程中用 puppeteer ,读取运行 docs.ts 渲染出组件,进行截图,然后随着云构建 CD 过程发到 CDN,就完事了!

最后,README 中加入一些特殊标记,在云构建过程中进行 README 替换生成就可以啦!并不会影响 README 本身要叮嘱的内容。

一个好的组件应该是什么样的?

最后,Duang !一份完整,漂亮,详细的文档就生成好了,整个过程我们并没有特意写过什么 README 方面的内容,一切都是非常轻松标准的进行输出。

一个好的组件应该是什么样的?

结语

在上面的一整套复杂的过程中,看上去最后好像我只得到了一个自动生成 README 的功能。但实际上呢,其实 README 只是一个顺带的产物。

整个过程中,我已经拿到了这个组件的所有我想要拿到的数据,它的 Props,Usage,Preview,版本信息,包名,甚至构建过程会同步发布该组件的 UMD CDN 包及 NPM 包。

接下来,就可以围绕这些数据和工具,建立和扩展很多功能和平台。

举几个栗子:

  • 建立一个 bit 一样的,组件平台,把团队内的组件收集起来,统一在平台展示及索引
  • 根据拿到 Props 类型信息做可视化的搭建平台,把 Props 的传参直接交给用户设置,根据不同数据类型提供不同的 Form Setter
  • 看似组件都分布在不同的库中,却可以通过组件 cli 做统一的构建处理
  • 非常轻松接入 微前端 框架,因为所有组件的发布构建都是标准的构建协议
  • 通过统计组件发布次数,下载次数,关联 bug 数评估代码质量

目前在我们团队,已经使用该工具产出 100+ 的可用组件,并且发布组件已经成功接入到我们已有的可视化编辑器中。

看一眼结合可视化设置面板后的效果吧:

一个好的组件应该是什么样的?

我发现只要实现过程中,没有给开发者带来太多的工作量,又能带来实时可以看到的效果,开发者会很乐意为那些 Props 做一番解释和修饰 ????。

我们团队目前产出的组件看起来一片通透,整齐明了。

我是一个热爱生活的前端工程师!Yooh!????

上一篇:这 10 道 Java 测试题,据说阿里 P7 的正确率只有 50%


下一篇:7-3 名字查找与类的作用域