AD UI
AD UI 的基本使命是帮助微信广告相关后台系统正确地成功。
为什么会有 AD UI?
AD UI 是从 components 独立出的项目,它包含且只包含微信广告基础 UI 组件。
components 组件库已经为我们解决的问题:敏捷开发,代码复用。
我将从 components 的开发现状出发,解释 AD UI 为什么需要独立存在,以及我们重新建立一套项目的目的。
components 的使用方式是 git submodule。现在的 submodule 实质上就是:云端共享文件夹。
现在这个文件夹:
- 承载一切可复用代码(React / Vue,
.scss
/ .less
); - loader / dependencies 依赖每一个业务项目;
- 敏捷开发,意味着没有开发流程(开发、测试、发布、issue),我们的历史,只存在于提交记录中;
- 多人维护,无开发规范。
我将这个文件夹下的 .js
文件分为两类:业务复用代码 与 基础 UI 组件。业务复用代码 指的是 color-picker、joyride、广告预览组件等;基础 UI 组件 指的是大多在 /basic/ 下的文件。
两者的区别在于,前者是或多或少个项目中使用;后者是需要长期维护的文件。
以上列出的 4 点,对于 业务复用代码 来说,其实没什么问题。由于复用代码和业务项目完全挂钩,开发流程、loader 这些问题,都倚靠业务项目。
submodule 在解决 业务复用代码 这个问题上,是敏捷的、高效的。AD UI 的建立并不是为了推翻 submodule,是因为 submodule 不适合 基础 UI 组件 这样一个完整的项目:
- 承载一切复用代码:基础 UI 组件 是需要长期维护的,它的技术框架和其他文件应该独立;
- loader / dependencies 依赖业务项目:基础 UI 组件 不应该与业务项目耦合,它需要在开发流程和使用流程上都独立。
- 没有开发流程:基础 UI 组件 需要自己的独立项目进行开发、测试、编译、发布、版本与 issue 管理、文档书写,比如从前我们没有单元测试,就没有衡量 “质量” 的方法。
- 多人维护,无组件评审与开发规范:基础 UI 组件 需要以一个长期可行的规范进行开发。
在经历了两年多的维护过程后,基础 UI 组件 因为没有版本、issue 等开发流程,也没有测试的过程,导致我很难去梳理与总结这个项目的发展历程。这是 AD UI 在今天终于出现的原因。
除了以上所说的 开发方式,我们也将从另一个方向对 基础 UI 组件 进行重构,即 组件设计。
- 对组件的整体结构再次进行梳理,如 Popover 弹出相关;
- 对 Stateless 组件重新设计,
getDerivedStateFromProps
,实现内部驱动 + 外部控制,以下会详细说明这两种设计方法; - 完善键盘与可访问性的建设;
- 视觉设计的 break changes。
components 对我来说从视觉设计、到组件设计,都是一次不正确的实践。
视觉设计方面,视觉设计师的原话是,components 做得 “不完整”。从结果上来看,break changes 也非常多。这并不是我在归咎于视觉设计,这本就是我们共同的项目。
组件设计方面,我对 prop 与 UI State 的理解不够充分,整体结构也存在许多问题。
components 并不是失败的,但它对我来说是不正确的。重新建立,就是我们要付出的代价。
我希望做正确的事,我希望对于每一个组件都填补从前 组件设计 上的缺陷;我希望对于 开发方式 做一次正确的实践。
对于将来,AD UI 也许是不正确的。但至少让我现在弥补从前的错误。
AD UI 在做出改变。只有做得好的东西才有资格去改变。AD UI 必须做好。
Ant:“Ant Design 不能保证你的业务一定成功。但是它能保证你的业务正确地成功,或是正确地失败。”
AD UI 的基本使命是帮助微信广告相关后台系统正确地成功。
🖥 环境说明
- adUI 基于的 React 版本目前为
^15.6.2
,基于 16,我们可以不需要 polyfill 地使用 getDerivedStateFromProps, fragments,createPortal 这三个对组件开发非常重要的特性,因为 mpweb 项目所依赖的 React 仍为 ^15.6.2
。 - IE 10 及以上。
📦 安装
tnpm install -S @tencent/adUI@latest
⌨️ 配置
adUI 对 scss 使用 css modules,因此需要在项目中配置。
配置示例之后加上。
🔨 示例
import { Button } from '@tencent/adUI';
ReactDOM.render(<Button />, mountNode);
⌨️ 本地开发说明
adUI 的开发任务:
- 建立 adUI 站点和组件文档;
- 书写 components 代码;
- 测试;
- 编译与发布。
具体说明如下:
IDE 相关
Vscode
插件需要:ESlint + styleint。
建立 adUI 站点和组件文档
adUI 的文档生成工具是 bisheng。bisheng 默认使用 browser history,为了能直接摆到 cdn 上,因此我修改了一下变成 hash-history:@tencent/bisheng-hash-history。
对于 site 文件夹的具体解释以后补全。
书写 components 代码
以 button 为例:
button
│ index.js 组件出口
│ Button.js 组件文件
└───__tests__ enzyme + jest 单元测试
│ │ ...
└───demo
│ │ basic.md md 会被 bisheng 解析,可在 md 中定义 order/title,以及书写 jsx 代码
│ │ others.md
└───style
│ button.scss scss 出口
│ base.scss 组件样式
组件的书写规范由以下方面限制:
- .js - eslint
- .scss - stylelint
- 为了方便组件文档的建立(如 API 表格),使用了 react-docgen 读取组件源代码。因此,组件必须有一句 title,以及每一个 prop 必须有文字说明。
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styles from "./style/index.scss"
/**
* 按钮用于开始一个即时操作。
*/
export default class Button extends Component {
static propTypes = {
/**
* 子节点
*/
children: PropTypes.node,
/**
* 附加类名
*/
className: PropTypes.string,
/**
* 是否禁用
*/
disabled: PropTypes.bool,
}
static defaultProps = {
children: "",
className: null,
disabled: false,
}
render() {
...
}
}
如果这个组件是有状态变化的(即不是 functional component),那组件需要实现内部驱动 + 外部控制。
- 内部驱动
内部驱动代表组件会使用自己的 state 完成状态变化,如
<Checkbox />
这样的 Checkbox 会自己完成勾选、反勾选,因为 Checkbox 内部有自己的 state。
如果想要 Checkbox 默认勾选,并且是内部驱动的,需要使用 prop defaultChecked
:
<Checkbox defaultChecked />
- 外部控制
外部控制代表组件完全根据外部的 prop 完成状态变化,
<Checkbox checked onChange={checked => console.log(checked)}>
这个 Checkbox 在每次点击时都会触发 onChange,但因为没有重新传入 checked prop,所以 Checkbox 的状态永远不会发生变化。在 mpweb 这样复杂的业务中很少使用内部驱动,而是用这样的方式使用:
const { checked } = this.state
<Checkbox checked={checked} onChange={checked => this.setState({ checked }) } />
内部驱动避免了业务逻辑中出现单纯为了控制组件 UI State 的逻辑。
比如:
<Tabs defaultValue={0} onChange={value => console.log(value)}>
<Tabs.Tab title="外层" value={0} />
<Tabs.Tab title="推广页" value={1} />
</Tabs>
如果使用者不想记录 Tabs 的切换状态,只是想在切换的时候得到组件的通知,这时候 Tabs 的 value 就是这里所说的 UI State。在以前的实现中,组件几乎都偏向于 PureComponent 的实现。这样,业务中每使用一次组件,就必须存储该组件的状态,因此就会产生非常多的,”只是为了让组件的表现正常“的多余状态出现在业务逻辑中。
这是非常错误和多余的。
内部驱动和外部控制的实现,使得 UI 组件状态非常完善和灵活。
测试
enzyme + jest 书写测试用例。正在学习。
关于测试:
- 必须包含:最基本的快照
expect(<Button />).toMatchSnapshot()
; - 必须包含:对内部驱动(defaultValue)和外部控制(value)的功能测试;
npm run test
是跑全部测试,并生成 coverage,本地查看:adUI/coverage/lcov-report/index.html
,请尽力提高 statements 的覆盖率;- 跑单一组件测试,如
jest components/button
,请确保全局安装了 jest
。
编译与发布
多人维护
- 透明的开发:前期的开发模式仍待规范,我自己都重复 refactor 过几次;
- Pull Request Review:为了更好地进行 review,需要将 jest snapshot 一并 request。
分支名称规范
待规范
提交规范
提交规范现在使用了 commitlint + husky
自动在 git pre-commit hook 时检测。同样在这时候会执行 stylelint 和 eslint。
未来会使用 commitlint 做自动化 changelog。此处感谢 charlieyin 推荐此工具。
现在推荐的 commit 示例:
新组件、功能(feature)
feat(Button): 添加 focus prop。
修复(bugfix)
fix(Button): 修复 focus 问题。
重构,无 feature & bugfix
refactor(Button): 调整 scss 结构。
文档相关
docs(Button): 添加 demo。
测试相关
test(Button): 添加快照。