做者简介:aoto 蚂蚁金服·数据体验技术团队javascript
摘要:以前写过一篇《TypeScript 体系调研报告》,通过半年多的蚂蚁金服数据平台大规模 JS 项目实战,沉淀了一些编程实战经验和感悟。java
TypeScript 是有类型定义的 JS 的超集,包括 ES五、ES5+ 和其余一些诸如泛型、类型定义、命名空间等特征的集合,为了大规模 JS 应用而生。对于 TypeScript 自己,更多信息请参考《TypeScript 体系调研报告》。本文只记录 TypeScript 在咱们实际项目中产生的一些实际有用的价值。咱们的项目基于 React 体系,所以本文着重关注 React 体系和 TypeScript 结合使用的经验。react
在 TypeScript 开发环境下写 React 组件,与 ES6 的区别主要就是 Props 和 State 的定义。若是是 ES6,大概是这样:git
import PropTypes from 'prop-types';
class App extends React.PureComponent {
state = {
aState: '',
bState: '',
};
}
App.propTypes = {
aProps: PropTypes.string.isRequired,
bProps: PropTypes.string.isRequired,
};
复制代码
若是用 TypeScript 来写,大概是这样(本文的 interface 定义默认以 I 开头):github
interface IProps {
aProps: string;
bProps: string;
}
interface IState {
aState: string;
bState: string;
}
class App extends React.PureComponent<IProps, IState> {
state = {
aState: '',
bState: '',
};
}
复制代码
TypeScript 自带 JSX 解析器,所以为了充分利用它自己的是静态检查功能,因此用泛型来定义 Props 和 State 的类型。如上定义后,在成员方法中经过this.props.
和this.state.
使用 Props 和 State 时能够智能提示,并且会作类型检查。看起来写没有变的简洁多少,但 TypeScript 来开发 React 应用有一个很大的优点,TS 环境中的 React 组件属性是静态的,也就是说能够作静态检查,而且在支持 TS 的 IDE 下写的过程当中能够自动自动提示(是否存在以及类型是否正确)/补全,**这对于保障大型 React 应用的代码质量和运行时质量颇有帮助。**而 ES6 环境中的 prop-types 属性是动态的,也就是运行期作检查,也不能作自动提示/补全。由此看来除了 JS 代码自己, TypeScript 对 React 组件开发也是颇有好处的。typescript
Redux 是最流行的状态管理库,通常和 React 结合使用。在 TypeScript 和 ES6 环境下,Redux 代码写法区别不大,有个问题就是,React 和 Redux 的绑定函数 connect 需用函数写法,而不建议用装饰器写法。由于 TS 对 类装饰器的静态解析还不支持,用了装饰器写 connect 就不能利用 TS 的静态解析的好处了。编程
const mapStateToProps = (state: {}, ownProps: IProps) => {};
const mapDispatchToProps = {};
export default connect(mapStateToProps, mapDispatchToProps)(App);
复制代码
Antd 是一个流行的 React 组件库,提供了 TS 类型声明,在写组件的时候能够进行属性的自动提示和检查,不肯定的能够进入类型声明文件查看,减小了查阅在线 API 文档的次数和属性拼写错误的可能性。框架
React + Redux + TypeScript 的编程体验:编程语言
复杂软件须要用复杂的设计,面向对象就是很好的一种设计方式,使用 TS 的一大好处就是 TS 提供了业界承认的类( ES5+ 也支持)、泛型、封装、接口面向对象设计能力,以提高 JS 的面向对象设计能力。函数
泛型,简单说就是泛化的类型。咱们项目中的泛型实践主要在 React 组件中,这也是借鉴了 React 的 Component 和 PureComponent 在 TS 中的定义。代码以下:
class Component<P, S> {}
class PureComponent<P = {}, S = {}> extends Component<P, S> {}
复制代码
P 和 S 就是泛型,这里的 P 是 Props,S 是 State。从这里能够看到若是咱们要基于 React 组件进行基于继承的设计,那泛型就能够发挥做用了。这里举一个例子,假如咱们要设计一个复杂 UI 模块,有两层继承,BaseComponent 继承 React.PureComponent,XComponent 继承 BaseComponent,效果就是 XComponent extends BaseComponent extends React.PureComponent
,示例代码以下:
export interface IBaseProps {}
export interface IXProps {}
export class BaseComponent<
IProps extends IBaseProps,
IState = {}
> extends React.PureComponent<IProps, IState> {}
export class XComponent<
IProps extends IXProps,
IState = {}
> extends BaseComponent<IProps, IState> {}
复制代码
BaseComponent 的属性(props)定义是 IProps,继承自 IBaseProps,这样 BaseComponent 组件中就可使用 IBaseProps 中定义的属性了。
XComponent 的属性定义是 IProps,继承自 IXProps,同时 XComponent 又继承自 BaseComponent,由于咱们在 BaseComponent 类的属性泛型定义中 IProps 是继承 IBaseProps,这样两个条件共同做用于 XComponent,就有了 IProps extends IXProps extends IBaseProps
的效果了,就能够在 XComponent 中同时使用 IBaseProps 和 IXProps 的属性了。
咱们都知道,封装在对于面向对象软件设计很是有用,而封装各个模块的实现细节,就能够在有效管理软件的复杂度。 TS 对于代码封装性的帮助主要体如今它提供了相似于 Java 的访问控制符。有了 private/protected/public
,咱们能够自主的控制类须要对外暴露的接口。访问权限需尽量严,也就是说无需对外暴露的用 private 或者 protected ,如无需被子类使用的就用 private,只有明确须要对外暴露的接口采用 public 来描述。在一些非 React 组件的公共类,封装特性尤其必要。
接口在面向对象设计里面也是很重要的,接口也就是对外提供服务的端口。咱们举一个例子,一个公共模块类 A ,提供了几个接口findById
,updateData
,destroy
。那么咱们要定义一个接口的定义,方便别的模块使用(这和传统编程语言的接口有点不一样,TS 的接口并不会本身运行):
export interface IClassA {
findById(id: string): IModel;
updateData(data: IModel): void;
destroy(): void;
}
复制代码
调用方:
const a = new IClassA();
a.findById('1');
a.updateData({ id: '1', name: 'a' });
a.destroy();
复制代码
有了接口声明,咱们在使用该模块的时候能够清晰的看到它到底有哪些接口,方法的入参是什么,返回值是什么。同时也有代码的自动提示,提高开发效率,减小拼写错误致使的低级 Bug。
如今流行的库如 React 、Redux 、 Lodash 、 Antd 等等都有 TS 类型声明,加上咱们本身业务代码完善的类型定义,整个代码库的健康度能够较好的保持住。这在传统的大规模 JS 应用里面是很可贵的。之前总感受 JS 一复杂,就感受质量难以保证,线上运行也有点虚,说不定何时就爆出一个 's' is undefined, 'b' is not a function 之类的错误。如今有了静态检查,内心更有底了。
上面章节已经就 TS 对面向对象设计能力的加强作了描述。设计的加强,是能够提高代码质量的。良好的设计面对需求迭代的不断冲击,能够保持代码的可维护性和可扩展性,也就提高了代码的质量和健康度。
如今的 Web 应用不少都是复杂的单页应用,尤为是一些工具类的产品,复杂度慢慢接近甚至不亚于一些传统的桌面软件,如 Word、Excel、SQL 客户端、数据集成分析工具等等,这些 Web 应用均可以说是大规模 JS 应用,须要一个团队来协做迭代开发。那么协做效率、代码可读性、可维护性、健壮性等等都是咱们应该重点专一的点。
协做效率自己比较难以衡量,但能够从阅读、维护别人代码的难度和效率这一点来讲明。若是有有完善的类型定义,再加上智能的 TS IDE(如 VS Code),从咱们项目的实践经验来看能够显著的提高代码的可读性、可维护性。不少时候都是看看源代码就能较容易的发现一些问题和 Bug。随着产品的不断迭代,一来一回之间就提高了整个团队的协做效率。
咱们项目的 TS 校验规则是很严格的,意味着代码中基本没有 any ,都须要定义具体类型。对于一些公共的模块,咱们也会严格定义 private、protected、public 等访问控制符,这意味着代码即文档,代码就能说明哪些是对外的接口,哪些是内部使用的。
一些经常使用的第三方库和框架都有完善的 TS 类型定义,同时整个项目业务代码都有完善的 TS 类型定义,静态检查出错的都会在 IDE 中提示出来,甚至能够阻断构建流程,这样能够减小 Bug 的产生,代码质量更为可控,代码健康度更高,内心也更有底了。
对咱们团队感兴趣的能够关注专栏或者发送简历至'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~