丁香医生小程序的组件库重构技术选型上采用 TypeScript 进行开发,对开源社区的 Vant-weapp (采用TS开发的小程序组件库)进行了详细的代码阅读,将过程中的一些收获与大家分享。
阅读Vant-weapp项目看到的一些亮点
- 组件统一去走构造函数,可以在其中做公共的处理,扩展微信组件的能力,如计算属性等等
- 很多小程序原生特性的使用
- 工程化
- 文档系统
项目的目录结构
见项目 Readme
抛出一个问题,是否有必要达成一些共识
- 开发目录 src
- 构建脚本目录 build
- 打包目录 dist
- typings
- .gitignore,.editorconfig
- package.json 中一些字段的默认填充
- Readme 中是否需要有目录结构的说明
TypeScript

个人对于 TypeScript 的理解,提供了一些 JavaScript 的语法补充,更多的是紧扣 Type 这个主题,在开发/编译阶段进行类型检查。
开发到目前阶段,组件库应用的特性不是特别的多,大多是 methods 工具函数的入参和出参定义,更多的高级特性应用还有待探索,慢慢给高级特性用起来,总结最佳实践。
有几个遇到的坑
关于接口有两段代码
第一段
interface Person {name: string;age: number;}let person: Person = {name: 'haha',age: 123,gender: 'man'}
第二段
function makeFriend(friend: Person) {console.log(friend);}let person1 = {name: 'haha',age: 123,gender: 'man'}makeFriend(person1);
小程序原生特性的使用
- WXS
- 组件间关系
- behaviors
- 内置behaviors,wx://form-field
- wx.createSelectorQuery
- NodesRef
- addGlobalClass
- 小程序生命周期
WXS 的应用
WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。
在每一个组件的 WXML 中都通过标签引入一个工具 wxs,可以在 WXML 中进行一些更为复杂的运算,这个项目中的应用主要是样式的计算,相对于之前很多复杂的三元运算简洁了许多。
截取一段示例代码
<wxs src="../wxs/utils.wxs" module="utils" /><view class="van-stepper custom-class"><viewclass="minus-class {{ utils.bem('stepper__minus', { disabled: minusDisabled }) }}"hover-class="van-stepper__minus--hover"hover-stay-time="70"bind:tap="onMinus"/>
再举一个例子,给定一个数组,模板中跑一个循环,在数组中的项目给定高亮样式。
组件的构造函数
每一个组件的构造函数都封装了一层,通过构造函数生成最后的配置,扩展了一些行为,完整的构造函数。
import { basic } from './mixins/basic';import { observe } from './mixins/observer/index';import { isObj } from './utils';function DxdComponent<Data, Props, Watch, Methods, Computed>(dingOptions: DxdComponentOptions<Data,Props,Watch,Methods,Computed,CombinedComponentInstance<Data, Props, Watch, Methods, Computed>> = {}): void {let options: any = {};const { relations } = dingOptionsoptions = { ...dingOptions };if (relations) {options.relations = {};Object.keys(relations).forEach(key => {options.relations[`../${key}/index`] = relations[key]})}// add default externalClassesoptions.externalClasses = options.externalClasses || [];options.externalClasses.push('external-class');// add default externalStyleif (isObj(options.properties)) {options.properties.externalStyle = String;}// add default behaviorsoptions.behaviors = options.behaviors || [];options.behaviors.push(basic);// map field to form-field behaviorif (dingOptions.field) {options.behaviors.push('wx://form-field');}// add default optionsoptions.options = {multipleSlots: true,addGlobalClass: true};observe(dingOptions, options);Component(options);}export { DxdComponent };
对构造函数包装了一层之后,可以将很多公共的部分写在其中,将原生的不友好的语法变的更加简单,是一个非常好的实践。
难懂的构造函数的类型约束
function VantComponent<Data, Props, Watch, Methods, Computed>(vantOptions: VantComponentOptions<Data,Props,Watch,Methods,Computed,CombinedComponentInstance<Data, Props, Watch, Methods, Computed>> = {}): void {// ... 函数体}
type CombinedComponentInstance<Data,Props,Watch,Methods,Computed> = Methods &LooseObject &Weapp.Component &Weapp.FormField &ComponentInstance & {data: Data & LooseObject & RecordToAny<Props> & RecordToReturn<Computed>;};type VantComponentOptions<Data,Props,Watch,Methods,Computed,Instance> = {data?: Data;field?: boolean;mixins?: Mixins;props?: Props & ThisType<Instance>;watch?: Watch & ThisType<Instance>;computed?: Computed & ThisType<Instance>;relation?: Relation<Instance>;relations?: Relations<Instance>;classes?: ExternalClasses;methods?: Methods & ThisType<Instance>;// lifetimesbeforeCreate?: (this: Instance) => void;created?: (this: Instance) => void;mounted?: (this: Instance) => void;destroyed?: (this: Instance) => void;};
第一眼看到的时候直接就懵了,其中有这样几个概念需要重点理解一下:

这一段类型是啥意思呢,从调用的地方慢慢来看
传入了几个泛型变量,vantOptions 参数的类型是 VantComponentOptions,又将泛型变量传入了 VantComponentOptions 类型中的 CombinedComponentInstance 类型,看一下 CombinedComponentInstance 的类型定义。
type CombinedComponentInstance<Data,Props,Watch,Methods,Computed> = Methods &LooseObject &Weapp.Component &Weapp.FormField &ComponentInstance & {data: Data & LooseObject & RecordToAny<Props> & RecordToReturn<Computed>;};
这里先忽略引用到的其他类型,通过泛型传递,在这里就将传入 components 构造函数参数的类型给确定出来。即 VantComponentOptions 定义中的 Instance 的类型。
type VantComponentOptions<Data,Props,Watch,Methods,Computed,Instance> = {data?: Data;field?: boolean;mixins?: Mixins;// 在这里通过 ThisType 来约束对象字面量中的 this 类型props?: Props & ThisType<Instance>;watch?: Watch & ThisType<Instance>;computed?: Computed & ThisType<Instance>;relation?: Relation<Instance>;relations?: Relations<Instance>;classes?: ExternalClasses;methods?: Methods & ThisType<Instance>;// typescript 提供一个显示的 this 参数,一个假的参数,出现在参数列表的最前面,这里通过 this 来约束函数中的 this 类型beforeCreate?: (this: Instance) => void;created?: (this: Instance) => void;mounted?: (this: Instance) => void;destroyed?: (this: Instance) => void;
