需求背景
最近在项目中遇到了一个类似 Collapse 的交互需求, 因此到 GitHub 上找了一圈关于 vue Collapse 的相关轮子, 但是多少都有些问题. 有的是实现问题, 例如 vue2-collapse, 伸缩部分采用 max-height 指定动画, 存在缺陷; 还有的是扩展性问题, 遇到定制场景比较棘手. 因此, 决定自己撸一个 Collapse 组件. 从项目中的一个需求, 到目前已将它开源并发布到 NPM, 还是踩了许多坑的. 代码虽然简单, 但是过程却不太容易. 因此这篇文章不是安利这款组件 https://github.com/DanceOnBeat/r-collapse-vue , 仅仅是想记录一下整个开发生命周期, 需要做什么, 以及遇到什么问题. 当然了, 如果这个组件或是这篇文章对你有帮助, 劳烦点进去给个 star, 万分感谢~
开发流程
我们的整个开发流程, 可以简单的总结如下:
项目脚手架搭建 (Vue CLI3)
组件功能开发
单元测试 (Vue Test Utils + Jest)
文档编写 (Vue Styleguidist + GitHub Pages)
发布 NPM
持续集成配置 (TravisCI)
我们来详细聊一聊每个过程是如何实施的, 且遇到了哪些问题.
脚手架搭建
脚手架我们直接使用 Vue CLI 来搭建即可, 其已经提供了丰富的功能, 并且可以通过 vue.config.JS 扩展 webpack 的能力. 但是要注意的是, 我们的构建产物是一个模块, 而不是我们平时在项目中构建出一个应用. 我们希望构建出来的模块是一个兼容 CommonJs 或是 UMD, 以便于使用者在不同的环境中引用. 所幸, Vue CLI3 也给我提供了这样一个功能, 详细可参考文档.
其次, 本次开发我选择了 TypeScript, 脚手架默认集成了. 使用之后直观的感受就是, Vue 的整个生态对 TS 的支持还不够完善, 但整体还是比较爽的, 期待官方在 3.0 中能够彻底支持 TS. 本文主题不是讨论 TS, 因此简单罗列下使用时遇到的问题:
在 template 中无法做到智能提示, 需要智能提示只能使用 tsx, 这一点是比较痛苦的
定义 Prop 时需要加非空断言 (!:), 否则会报错, 例如:
- @Prop({
- required: true
- })
- public value!: String;
使用 Vue Test Utils 写单测时, 无法对自定义的 Vue 组件进行类型推导, 见下文
使用 Vue Styleguidist 编写文档 demo 不支持 TS, 见下文
组件功能开发
在日常写业务的时候, 我们可能会在组件当中耦合很多的业务逻辑. 但是作为一个通用组件, 我们在开发的时候要尽可能保证它的扩展性, 因此我们希望达到的一个目标就是: 在保证开发体验的前提下提高扩展性. 对于 Collapse 组件, UI 方面一般都是按照各自的设计稿来自行编写的, 因此我们只需要提供功能即可. 更好的方式是提供默认的 UI, 但又可以支持完全定制, 这个是目前 r-collapse-vue 可以完善的一个点.
在进行功能设计的过程中, 我们要先确定我们需要支持哪些功能, 以 r-collapse-vue 举例, 需要提供的功能包括:
基本的展开 / 收缩 (支持动画)
手风琴模式
自定义点击事件
Collapse 嵌套
在实现的过程中, 我们也需要思考很多细节, 举几个例子:
使用者如何控制每一个 Collapse 的状态?
最简单的想法是传递一个类似叫做 status 的 prop, 在每一个 Collapse 内部去维护这个状态. 但是这样会有一个问题, 我们如何去支持手风琴模式, 即一个展开另外的都需要收起. 按照这种做法, 需要用一个父组件包裹, 去获取每一个 Collapse 子组件的实例, 调用实例方法去控制. 这样做不是不行, vue2-collapse 就是这么做的, 但是我认为不够优雅. 因此我们重新整理思路, 每一个 Collapse 之间的状态可能会互相影响, 我们常用的解决方法是状态提升, 因此我的做法是抽象两个组件, Collapse 和 CollapsePanel,Collapse 即是父组件, 提供状态控制, 将状态传递给其内部嵌套的 CollapsePanel, 在内部消化掉所有的逻辑, 这更加符合单向数据流的思想, 站在使用者角度来看, 写法也能够相对统一, 使用时我们只需这么写:
- <r-collapse v-model="activeKeys">
- <r-collapse-panel name="a">xxxx</r-collapse-panel>
- <r-collapse-panel name="b">xxxx</r-collapse-panel>
- </r-collapse>
实际场景中经常会对展开和收缩进行样式区分, 如何帮助使用者提升开发体验?
见上面的代码, 我们在 CollapsePanel 中传入了一个 name 属性作为唯一标识, 此时使用者可结合 activeKeys 自行判断当前 panel 是否展开:
- <r-collapse v-model="activeKeys">
- <r-collapse-panel
- name="a"
- :class="activeKeys.includes('a') ?'active':''"
- >
- xxxx
- </r-collapse-panel>
- <r-collapse-panel name="b">xxxx</r-collapse-panel>
- </r-collapse>
这种方法虽然可以, 但是存在两个问题:
用户需要自行添加逻辑, 体验不够友好
每次重新渲染都会执行额外的逻辑判断, 性能不够友好
因此, 可以提供一个 activeClass 的 prop, 让使用者可以自定义展开状态的类名, 就可以避免以上的问题.
这些细节问题看似简单, 但是作为一个通用组件的开发者, 我们应该经常站在使用者的角度看问题, 才能不断地提升组件的开发体验.
单元测试
一个优秀的开源组件一定少不了单元测试, 例如 Ant Design 等开源库都有着很高的单测覆盖率. 一开始写单测可能会觉得耗时, 没有必要, 但其实单测能够带来诸多的好处:
单测相较手动测试, 能够减少 bug 率, 覆盖的场景更全, 且测试较为方便
开源的组件可能会有很多的维护者, 单测能够降低模块之间互相影响产生 bug 的概率
使用者一般都会选择单测覆盖率较高的轮子
因此, 单测必不可少, 目前前端常见的选择包括:
Jest,Facebook 出品, 配置简单, 使用 JSDOM 模拟测试环境, 当遇到操作真实 DOM 的场景, 如获取 scrollHeight 等比较乏力
Karma + Mocha,Mocha 同 Jest 都是测试框架, 而 Karma 为框架提供了真实的浏览器测试环境, 如果代码中对 DOM 操作较多, 建议使用这种组合. 但是 Mocha 配置较复杂, 且需要自行安装断言库
Vue 当中已经给我们提供了单测相关的工具 Vue Test Utils, 它提供了很多功能, 如组件挂载, 获取实例等等, 使用它配合 Jest 或者 Mocha 能够比较方便的完成单测, 详情参考文档 https://vue-test-utils.vuejs.org/zh/ .
在编写单测时, 我们需要注意, 对于 UI 组件来说, 不应一味追求行级覆盖率, 应当只关注输入输出, 避免涉及过多的实现细节, 从而避免琐碎的测试. 例如, 我们测试展开功能, 只需要触发 click, 检测 status 是否为 true 即可, 无需关注过程中是触发了 xxx 事件还是发生了其他事情, 这样当我们的逻辑修改后能够保证单测还能有效. 同时, 在用 TS 编写单测时, 通过 Vue Test Utils 创建的 wrapper 是普通的 Vue 类型, 因此自定义的 Vue 组件无法进行类型推导, 此时要获取实例属性时需要通过 (wrapper.vm as any).xxx 来获取. 经查阅资料, 官方表示目前没法解决这个问题, 只能使用这种方式.
文档编写
一个好的文档能够方便使用者明白你的设计理念, 因此我们想要的文档不仅需要有完整的 API 描述, 并且在展示 demo 时能够同时展示源码, 类似于在 Ant Design 或 Element 中那样. 我们这边使用的是 Vue Styleguidist https://vue-styleguidist.github.io/ .
它通过 vue-docgen-API, 能够将注释转换成属性描述展现在页面上. 因此我们只需要写注释, 就能够生成组件属性相关的文档. 而我们的另一个需求, 在展示 demo 时能够同时展示源码, 它也能够做到. 我们可以通过两种方式:
在 Vue 组件中使用 < docs></docs > 标签来写 demo, 这样做对组件有侵入, 感觉不太好
新建一个 Markdown 文件, 内部通过特殊的标记写入 vue 代码即可
我们选用第二种方式, 但是又遇到了许多坑. 比如写入 md 的 Vue 代码不支持 TS, 试了很多的方法都没有解决, 后来还是改成了 JS 写法; 还有 SCSS 使用嵌套时, 嵌套的内容未被正确解析, 后改成了 CSS. 其实这个东西的实现难度并不高, 在 md 中写 Vue 无非就是写个 webpack 插件解析. md 格式的文件, 取出 Vue 的部分通过 vue-loader 处理, 鉴于 bug 这么多且样式我认为不够美观, 之后有时间可以再造个轮子玩一玩.
在 Vue CLI3 中使用 Vue Styleguidist 十分方便, 只要运行:
vue add styleguidist
然后在 package.JSON 的 scripts 中添加:
- "serve:doc": "vue-cli-service styleguidist",
- "build:doc": "vue-cli-service styleguidist:build"
就可以拆箱即用了.
文档编写完成, 我们执行 yarn build:doc 构建文档, 发现输出的是一个 html 文件, 此时我们可以选择使用 GitHub Pages 来作为我们的静态资源服务器展示文档, 因为它方便部署且免费. 过程如下:
将 styleguide.config.JS 中的 styleguideDir 选项改为 "docs", 即将 build 的目标目录设置为 docs
在 GitHub 对应仓库的 settings 中将 GitHub Pages 的 Source 选项设置为 master branch/docs folder, 意味着会自动从仓库的 docs 目录获取静态资源
这样每次更新 docs 会自动部署更新文档.
说完文档, 我们还需要编写在 GitHub 上展示的 README, 这里推荐一个生成 README 的库, https://github.com/kefranabg/readme-md-generator , 格式非常简洁且美观. 在 README 中, 我们可以添加如下的小图标:
这个可以使用 https://shields.io/category/build 生成, 它能关联你的 NPM,GitHub 等等, 实时更新 icon 信息, 有了它文档逼格瞬间高多了.
发布 NPM
要将包发布到 NPM, 我们需要做如下的准备工作:
到 https://www.npmjs.com/ 上注册一个 NPM 的账号
本地执行
NPM login --registry=https://registry.npmjs.org
注意, 这边加上 registry 为了防止在全局或当前环境覆写. npmrc, 导致登录的不是 NPM 源.
修改 package.JSON 的配置, 可以参考 v-collapse-vue 的部分配置:
- {
- "name": "r-collapse-vue",
- "version": "1.0.0",
- "description": "a collapse component for VueJs",
- "author": {
- "name": "Ray",
- "email": "zhurui0904@gmail.com"
- },
- "main": "dist/r-collapse-vue.common.js",
- "files": [
- "dist"
- ],
- "keywords": [
- "Vue",
- "collapse"
- ],
- "publishConfig": {
- "registry": "https://registry.npmjs.org"
- },
- "repository": {
- "type": "git",
- "url": "git@github.com:DanceOnBeat/r-collapse-vue.git"
- }
- }
最后执行 NPM publish 即可完成发布
每次发布新版本之前, 我们可以通过
NPM version major/minor/patch -m 'xxx'
来修改版本号并且打上 tag, 此 tag 非 NPM 的 dist-tag, 而是 Git 的 tag. 一个版本对应一个 tag, 并通过
Git push origin master --tags
将 tag 也推到远程仓库, 这样在仓库中我们就能清楚地看到发布的记录, 方便日后回滚之类的操作. 具体的版本规则可以参考 semver 规范 https://semver.org/lang/zh-CN/ .
持续集成 (CI)
当开发结束后, 我们需要跑测试, 测试通过后, 还需要构建生成 dist 目录, 最后发布到 NPM. 每次修改都做这样一套操作实在繁琐, 并且容易遗漏步骤, 这时候我们就需要使用 CI 将我们的流程自动化, 我在这边选择了 TravisCI https://docs.travis-ci.com/ . 同时, 我们还可以通过 Codecov https://codecov.io/ , 将我们的单测报告上传至 Codecov 服务器, 这样就能同步更新 Codecov 的 icon.
在配置 CI 时, 我原本将生成 docs 的步骤也添加了进去, 此时我们在 deploy 中会有两个步骤, 如下:
- deploy:
- - provider: NPM
- email: zhurui0904@gmail.com
- api_key: $AUTH_TOKEN
- on:
- tags: true
- branch: master
- skip_cleanup: true
- - provider: pages
- skip_cleanup: true
- github_token: $GITHUB_TOKEN
- keep_history: true
- target_branch: master
- on:
- branch: master
这会造成一个问题是 provider: pages 会将 CI 服务器生成的新的 docs 目录 push 到我们的 GitHub 仓库, 这又会触发一次 CI, 以至于无限循环. 后来也没找到合适的解决方案, 又考虑到文档不经常更新, 就将文档部署相关的部分从 CI 中移除了. 如果大家有合适的解决方案, 可以留言告诉我一下, 不胜感激.
之前我们提到一个 NPM 发布版本对应一个 tag, 因此我们可以在配置中添加
if: tag IS present
限定只在提交了 tag 才触发一次自动化构建, 这样基本上就大功告成了.
总结
这是一次非常有趣的造轮体验, 代码虽然不难, 但是过程中又学习到了很多新的东西, 包括单元测试, 文档编写等等, 希望这篇文章能给准备造轮或想要造轮的小伙伴提供一点帮助.
来源: https://www.cnblogs.com/danceonbeat/p/11063773.html