Typescript 是如何保证前端质量的

Typescript 是微软于 2014 年发布的基于 Javascript 的超集,和 Babel 将 ES6 语法编译成 ES5 一样,Typescript 也会把 TS 的语法编译成从各种目标代码(一会儿继续说这事儿)。

开发目标

我们很清楚 ES6 只是 ES5 的扩展,尽管 Chrome 等浏览器已经率先实现了部分 ES6 功能,但依然需要通过 Babel 进行编译,才能对旧版的浏览器提供支持,其实我个人觉得它除了解决部分开发效率,对于 Javascript 弱类型的实质没有任何改进,从产品质量保证而言,Babel 提供了编译时的语法检查,但是能力仅限于检查未定义变量,而浏览器中直接运行的 ES6 语法,和 Javascript 一样是纯粹的动态语言,最基本的检查能力都不具备。

回到 2014 年,那是个 ES6 语法还未成型的年代,当时有句话叫做“动态语言一时爽,重构时候火葬场”,各大厂商已经认识到了 Javascript 的动态特性无法支撑大型项目的开发,纷纷提出了自己的解决方案,例如 Google DartFacebook flow.js 以及本文要介绍的 Microsoft Typescript

笔者认为,Typescript 是最合适的解决方案,它很简单地为 Javascript 赋予了单个对象赋予了类型、对象赋予了 interface、为目前现有的 Javascript 库赋予了 Declaration File 使他们全部都获得了静态的类型系统,与 ES6 语法基本兼容,比重新设计整个语言的 Dart 更轻,但比 flow.js 更重,配合官方免费的、跨平台的 VisualStudio Code 更是将整个开发生态打造得无可挑剔(另外说一句这个编辑器也是 Typescript 开发,基于 Electron 的 Web 应用)。

一个简单的范例

ts-node

我们可以通过 tnpm install -g ts-node 来体验 typescript,范例代码是一个很常见的场景,做数据运算的时候,经常会有数据类型不对的情况,Typescript 对于直接的数据操作并没有类型检查,但当生成一个函数,并且对参数赋予类型时,便会在编译时进行类型检查,对于不符合类型要求的地方,会直接抛出错误,中止编译过程,同时我们还可以看到,它对 Javascript 内置的函数都已经做了基本的类型声明,parseInt(value) 后会是一个 number,符合了函数的入参类型要求,便正确输出返回值。

是否有一种 Java 的既视感?通过静态类型声明,就具备了和 Java 一样的开发大型应用的能力,

基本配置

Typescript 比较好的地方是,编译器本身只有 typescript 一个包,通过 tnpm install -g typscript 将会安装 v2.0.10 稳定版(截止发稿时),安装之后,系统中将会多出一个 tsc 命令,它是 Typescript 的编译器。

可以写一个很简单的代码,进行编译测试。

let value: string;
value = 'Hello world';
const printStr = (str: string) => {
  console.log(str);
}
printStr(value);

保存为 helloworld.ts,然后直接执行 tsc helloworld.ts,将会输出成默认的 ES3 javascript

var value;
value = 'Hello world';
var printStr = function (str) {
  console.log(str);
};
printStr(value);

Typescript 是具备直接输出 ES6 能力的,只需要在编译时加上 -t es6 参数,便可以输出 ES6 的目标文件,从输出的 js 文件和 ts 文件对比,就会发现 ts 只是比 js 多了个参数类型定义。

let value;
value = 'Hello world';
const printStr = (str) => {
  console.log(str);
};
printStr(value);

编译参数可以直接在命令行后面加上,更多参数可以参考编译选项,也可以通过 tsconfig.json 直接定义,首先可以使用 tsc --init 生成初始化的配置文件,我这里加了 files 用于定义输入的源代码。

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "noImplicitAny": false,
    "sourceMap": false
  },
  "files": [
    "helloworld.ts"
  ]
}

然后直接使用 tsc 就可以进行编译了,更多编译参数,请参考 tsconfig.json 文档

需要特别说明的是以下几个参数

参数名称可选值说明
targetES3、ES5、ES2015输出的 Javascript 代码类型,默认是 ES3,如果只兼容到 IE9 以上,用 ES5 即可,Typescript 生成的代码质量已经很高了,以前还需要用 Babel 做后端再二次编译一次,现在完全无需 Babel。
modulenone、commonjs 、amd 、system、umd 、es2015输出的模块系统,可以看到支持得很全面,最新式的 system.js 也支持到了,需要说明的是,有一个 --outFile 参数可以讲所有代码打包成一个文件(可以代替 webpack),但是当使用它打包时,只能使用 amd 或者 system 这种支持异步加载的模块类型。
jsxReact、Preserve处理 jsx 的方案、React 可以讲所有 jsx 编译成 React.createElement 的形式,Preserve 则保留原有格式

配合 webpack

Typescript + Webpack 使用非常简单,和 Babel 非常类似,只需要加上 ts-loader 或者 awesome-typescript-loader 这两个 loader 各有千秋,其实目前 Typescript 直出 ES5 已经非常成熟,用 ts-loader 即可,如果有需要使用 Babel 进行 ES6 到 ES3 编译的可以使用 awesome-typescript-loader 据说有更好的性能和特性。

这里有一份 webpack 范例配置文件

语法 Linter

Linter 的作用是保证多人开发时的语法的一致性,它可以在编译前进行语法检查,找出不合规的地方,并给出 Warning,这些不合规的地方未必会影响代码运行结果,但是当多人开发时,保持一致的代码风格还是很有必要的。

和 Javascript 一样,Typescript 也有 linter,叫做 tslint,它提供了语法检查和开发指导。

使用 tnpm install -g tslint 之后,会增加 tslint 命令,可以使用 tslint --init 生成 tslint 的默认配置文件,我们用它来检查一下刚出的 helloworld.ts

» tslint helloworld.ts 
helloworld.ts[2, 9]: ' should be "
helloworld.ts[5, 2]: Missing semicolon

很明显,它提示了第二行的单引号需要改为双引号,同时第五行少了一个分号。

实际开发之中是不会使用默认的宽松配置的,tslint 已经提供了大量参考配置,我们一般使用“推荐”配置,可以参考 tslint.json 它从代码的考虑已经做了大量优化,可以作为项目中的推荐方案。

语法简介

Typescript 语法与 ES6 语法基本一致,const、let 箭头函数可以直接使用,比较出色的地方是它不需要增加插件便可以实现一些高级语法编译,例如 async 和 await,相对于 Babel 我感觉 Typescript 编译出的代码更佳简单干净,可读性高。

和 ES6 不一样的地方,是它增加了类型系统,这又主要分以下几种类型定义方式。

变量类型系统

在 Typescript 中,声明变量时如果直接赋值,则会使用自动类型判断固定该变量的类型,例如:

const foo = 123; // number
const bar = 'abc'; // string

如果需要声明一个变量,但不赋值,就必须给它声明一个类型,当后期使用类型不符合时会抛出错误。

let foo: number;
foo = 123; // Ok
foo = 'abc'; // Type 'string' is not assignable to type 'number'. (2322)

Typescript 的基本类型主要有:

类型范例说明
booleanlet isDone: boolean = false;布尔值,只允许 true、false
numberlet decimal: number = 6;数字类型,包含正数、浮点数和16进制数
stringlet color: string = "blue";字符串
Arraylet list: number[] = [1, 2, 3];数组,需要说明的是数据也需要声明内容的类型,写法就是内容类型加上 []
Tuplelet x: [string, number];强制数组内的元素类型
Enumenum Color {Red, Green, Blue};枚举类型
Anylet notSure: any = 4;不限制变量类型,如果使用它就是普通的 JS,应该去声明每一个变量的类型,避免使用 any。
Voidlet unusable: void = undefined;空值,只允许赋予 null 和 undefined
Null and Undefinedlet u: undefined = undefined;比 void 更佳详细,null 或者 undefined 只可选其一
Never 主要用于声明一个无返回值的函数

更多范例,请参考官方基础类型文档。

对象 Object

对于 Object 的类型定义,主要通过 interface 关键字进行定义,和基本变量定义类似,interface 也非常简单。

interface IHelloWorld {
  hello: string;
  world: number;
  foobar?: void; // Optional property
}

const helloWorld: IHelloWorld {
  hello: 'Hello',
  world: 123,
}

console.log(JSON.stringify(helloWorld));

helloWorld.foobar = null;
console.log(JSON.stringify(helloWorld));

helloWorld.test = 123;
console.log(JSON.stringify(helloWorld));

直接使用 ts-node 运行会发现编译不过,抛出了错误

test.ts(17,12): error TS2339: Property 'test' does not exist on type 'IHelloWorld'.

是因为在最后我们给 helloWorld 赋予了一个 interface IHelloWorld 中不存在的 test property,把它删掉就可以正常编译运行了,由此可见 Typescript 的严谨。

类 property 类型声明、方法私有性声明

和 ES6 一样,Typescript 也提供了 class 关键字用于声明累,而 property 类型声明借鉴了初始化值的语法,直接在 constructor() 之上,像初始化变量一样进行类型赋予即可。

class Foobar {

  private str: string; // this.str type

  public constructor(str) {
    this.str = str;
  }

  public printStr() {
    console.log(this.str);
  }
}

const foo = new Foobar('Hello world');
foo.printStr();

这里还能对方法的私有性进行定义,当不慎掉用到 private 方法时,编译器就会报出错误阻止编译过程,有效保护私有方法。

第三方库接口类型定义 Declaration File

Typescript 因为其特点,所以对第三方库提供的接口也有强类型的需求,但是老的第三方库往往都是使用 Javascript 进行开发,并没有声明接口类型,微软采用了一个取巧的办法,给第三方库增加了一个 .d.ts 的类型声明文件。

社区里已经有了绝大多数常用库的类型声明文件,保存在 DefinitelyTyped 仓库里,可以直接使用 tnpm@types private repo 进行安装,例如 tnpm install @types/react-bootstrap 安装 react-bootstrap

类型声明文件还有一个好处是它在声明类型的同时,还可以对函数的用法进行说明,这样开发起来不用查看源代码或者官方文档,在 IDE 里就能了解方法的功能。

但遇到比较冷门的第三方库,没有 d.ts 文件提供时,直接 import 它会提示找不到 module,对于比较小的第三方库,建议自己用 Typescript 重写,也可以自己开发 d.ts 文件进行类型定义,Typescript 2.0 对 d.ts 文件进行了大量简化,具体开发内容有点大,可以参考 官方文档,未来或许会单独写一篇《Typescript Declaration File 开发指南》。

成功案例

因为 Typescript 静态类型的特性,各大公司都在积极使用 Typescript 进行项目开发。

  1. Google 的 Angular 2
  2. 蚂蚁金服的 Ant.design
  3. Teambition

目前我们组已经在内部使用 Typescript 进行项目开发,目前主要成果有:

  1. vincenzheng 的微信小程序脚手架
  2. xqkuang 的 react-redux 脚手架
  3. xqkuang 的 NodeJS 服务器框架(进行中)
  4. xqkuang 的腾讯指数统计埋点

版权所有丨转载请注明出处:https://kxq.io/archives/typescript是如何保证前端质量的