背景
typescript 是微软发明的,像js是网景公司发明的,
每一门语言都是人发明的,由于是人发明的,人总是会犯错,
所以每一门语言都会有一些错误,那typescript主要就是为了解决js的一些错误或认为它不好的地方
TS是什么

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript。
TypeScript是一个编译到纯JS的有类型定义的JS超集
JavaScript 和 TypeScript 的主要差异
TypeScript 可以使用 JavaScript 中的所有代码和编码概念,TypeScript 是为了使 JavaScript 的开发变得更加容易而创建的。例如,TypeScript 使用类型和接口等概念来描述正在使用的数据,这使开发人员能够快速检测错误并调试应用程序
- TypeScript 从核心语言方面和类概念的模塑方面对 JavaScript 对象模型进行扩展。
- JavaScript 代码可以在无需任何修改的情况下与 TypeScript 一同工作,同时可以使用编译器将 TypeScript 代码转换为 JavaScript。
- TypeScript 通过类型注解提供编译时的静态类型检查。
- TypeScript 中的数据要求带有明确的类型,JavaScript不要求。
- TypeScript 为函数提供了缺省参数值。
- TypeScript 引入了 JavaScript 中没有的“类”概念。
- TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。
安装与调试
npm install typescript@2.9.2 -g
安装完之后我们会得到两个命令tsc和tsserver
tsc: TypeScript Compiler 把ts变成js的程序

npm install ts-node@7.0.0 -g
让node支持typescript
注意记下 ts-node 安装后的可执行文件路径,后面要用的

为什么用TS
从开发效率上看,
虽然需要多写一些类型定义代码,但TS在VSCode、WebStorm等IDE下可以做到智能提示,智能感知bug,同时我们项目常用的一些第三方类库框架都有TS类型声明
解决了 IDE/编辑器无法智能提示的痛点。

使用第三方库时方便查看文档(使用 TS 的过程就是一种学习的过程)
- 老是将数组的slice和splice方法搞混

- 使用Uniapp的 弹框组件,配合idea快速了解api需要哪些参数,每个参数是什么类型, 且参数代表什么含义


配合IDE/编辑器 智能感知bug
-
声明变量时需要指定类型

-
接口规定了对象中要包含的属性(不能多也不能少)

-
enum枚举类型的属性 只读

-
拼写错误

从可维护性上看
长期迭代维护的项目开发和维护的成员会有很多,团队成员水平会有差异,而软件具有熵的特质,长期迭代维护的项目总会遇到可维护性逐渐降低的问题,有了强类型约束和静态检查,以及智能IDE的帮助下,可以降低软件腐化的速度,提升可维护性,且在重构时,强类型和静态类型检查会帮上大忙,甚至有了类型定义,会不经意间增加重构的频率(更安全、放心)
从线上运行时质量上看
现在的SPA项目的很多bug都是由于一些调用方和被调用方(如组件模块间的协作、接口或函数的调用)的数据格式不匹配引起的,由于TS有编译期的静态检查,让我们的bug尽可能消灭在编译器,加上IDE有智能纠错,编码时就能提前感知bug的存在,我们的线上运行时质量会更为稳定可控
- rollbar 是一个异常监控平台,它列举了前端项目中top10的错误类型 文章结尾处也说明了,使用Ts时可以避免很多低级错误
TS能干点啥
类型系统
js七种数据类型 + any + enum + void + never

let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
// 数组,有两种写法
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
// 元组(Tuple)各元素的类型不必相同
let x: [string, number] = ["hello", 10];
// 枚举
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
// 不确定的可以先声明为any
let notSure: any = 4;
// 声明没有返回值
function warnUser(): void {
alert("This is my warning message");
}
let u: undefined = undefined;
let n: null = null;
// 类型永远没返回
function error(message: string): never {
throw new Error(message);
}
// 程序员自身 主观判断 h是一个字符串(这个判断不一定对),ts会相信你,如果程序报错就是程序员的锅
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;
类型编译检查
- 运行时报错: 浏览器读取html,遇到 script标签,下载并运行js,bug只会在运行时发现
- 编译时报错: 在编译的时候报错,提前处理bug, 而不是当用户执行脚本时才报错
- .java --javac--->.class字节码文件, jvm运行.class文件
- .ts --tsc --->.js文件, 浏览器运行

类型推断
让我们既能享受js的灵活又能享受ts的类型检查
// 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
let myFavoriteNumber = 'seven';
//等价于
let myFavoriteNumber :string= 'seven';
面向对象编程增强
面向对象(oop)的三大特征: 封装,继承,多态(抽象类, 接口, 泛型)
封装: 隐藏内部实现细节,对外只暴露接口,调用方只需按接口规定调用即可,封装的重要手段就是访问控制权限,在ts中为 public/protected/private
继承:子类继承父类,子类拥有父类中除了private声明的所有属性(创建子类时调用父类的构造方法)
多态: 多态是由于继承或实现而引出的,继承者可以重写父类的方法, 实现者可以实现接口中的抽象方法, 当用父类的引用指向子类的对象时,就形成了向上造型,从而形成了多态

访问权限控制
TS中的类比js中的类多了 抽象类和访问权限限制 两个概念

JS面向对象编程的一个大问题就是没有提供原生支持信息隐藏的方案(很多时候都是通过约定编码方式来做)

私有的成员变量,公共的get/set方法
class Demo7 {
private _name:string;
private _age:number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
get age(): number {
return this._age;
}
set age(value: number) {
this._age = value;
}
}
接口
TS中的一个核心原则之一就是类型检查关注的是值的形状,有时就叫做“鸭子辨型”(duck typing)或“结构化子类型”(structural subtyping)。TS中interface就承担了这样的角色,定义形状与约束,在内部使用或者和外部系统协作
接口在定义的时候,不能初始化属性以及方法,属性不能进行初始化,方法不能实现方法体。 类实现接口之后,必须声明接口中定义的属性以及方法。
/**
* 接口就是用代码描述一个对象必须包含什么属性(包括方法), 不能多也不能少
*/
interface Shape {
head: string,
body: string
}
interface Human {
name: string,
age: number,
shape: Shape,
say(word:string): void;
}
interface Man extends Human3{
gender:string
}
let h: Man = {
name: '张三',
gender: '男',
age: 27,
shape: {head: '头', body: '身体'},
say(word: string): void {
console.log(word)
}
}
// 可选属性
interface SquareConfig {
color?: string;
width?: number;
}
// 只读属性
interface Point {
readonly x: number;
readonly y: number;
}
类实现接口之后,必须声明接口中定义的属性以及方法,
interface Animal11 {
name: string;
eat: () => void;
}
class Dog implements Animal11 {
name: string = '小白';
eat() {
console.log('tag', 'I love eat bone!')
}
}
const dog: Dog = new Dog();
dog.eat();
数据库中的一条记录对应java中的一个实例对象,该类中的字段和字段类型与表中的字段及字段类型匹配, 有了TS的接口后,我们可以用 接口和 后端的实体类(或接口返回的数据) 一一对应,这样就不会出现接口返回属性找不到,或接口返回数据类型不匹配的问题
抽象类
/**
* Abstract class 抽象类(class能够拥有的 abstract class 都拥有)
*
* 抽象类中可能包含抽象方法(只有方法名,没有具体实现的方法),所以抽象类不可以实例化, 一般都作为父类
* 反证法: 抽象类如果可以实例化,实例化出来的对象如何执行抽象方法(没有方法)
*
* class --> abstract class --> interface
* 抽象程度逐渐提升,interface 抽象程度最高
*/
abstract class Animal {
kind:string;
constructor(kind: string) {
this.kind = kind;
}
breath():void {
console.log('呼吸')
}
// Method 'birthType' cannot have an implementation because it is marked abstract
// 抽象方法不能有具体实现
abstract birthType():void
}
class Human1 extends Animal {
// 子类实现抽象父类中的抽象方法
birthType(): void {
console.log('胎生')
}
}
泛型
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件
总结一句话: 当我们需要支持多种类型时,用T当占位符,这个T就是泛型,代表广泛的类型
/**
* 泛型就是用一个东西表示广泛的类型。可以理解为一个占位符
*/
function returnIt<T>(arg: T): T{
return arg;
}
let s = returnIt<string>('hi')
let n = returnIt<number>(123)
/**
* 泛型接口
*/
interface genFn<T> {
( arg : T ) : T;
};
let ide : genFn<number> = identity;
/**
* 泛型类
*/
class Gen <T> {
zero : T;
add : ( x : T, y : T ) => T;
}
let gen = new Gen<Number>();
/**
* 泛型约束: 就是给泛型添加一些约束
* 例如: 并不是任意的类型T的实例 都具备length属性, 我们先创建一个接口Lengthwise, 则T extends Lengthwise 时, T一定具备length属性
*/
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
T vs any
// T代表广泛的类型, 参数是T类型,返回值是T类型,一一对应
function f1<T>(arg:T):T {
return arg
}
let s1 = f1('hello')
s1.length;
// 如果使用any的话 无法做类型推断
function f2(arg:any):any {
return arg
}
let s2 = f2('hello')
其实数组接受的就是一个泛型
let ss:Array<string> = ['hello']

函数
/**
*你会发现用ts写函数会很放心,因为我 入参类型写死了,返回值类型也写死了,唯一有可能出错的地方就是我的逻辑代码
* 也方便重构,n年之后回头再看这个函数,一目了然, 如果用js的话,你可能要去差这个函数到底返回什么类型的值
*/
function add(a:number, b:number):number {
return a + b
}
可选参数
/**
* TypeScript里的每个函数参数都是必须的,而编译器检查用户是否为每个参数都传入了值
* JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。 *
* 在TypeScript里我们可以在参数名旁使用 ?实现可选参数的功能
*/
function hi2(name:string, age?:number) {
console.log(`Hi, ${name}, ${age}`)
}
hi2('张三', 27)
重载
/**
* 重载: 函数重载的意义在于能够让你知道传入不同的参数得到不同的结果
*
* 关于函数重载,必须要把精确的定义放在前面,
* 最后函数实现时,需要使用 |操作符或者?操作符,把所有可能的输入类型全部包含进去
*/
// 上边是声明
function add1 (arg1: string, arg2: string): string;
function add1 (arg1: number, arg2: number): number;
function add1 (arg1: string | number, arg2: string | number) {
// 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2
if (typeof arg1 === 'string' && typeof arg2 === 'string') {
return arg1 + arg2
} else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
return arg1 + arg2
}
}
// 参数要么同时为 number 要么同时为 string, 因为只定义了这两种
// add1(1, '1')
重载和重写有什么区别?
类型声明文件
由于非常多的JavaScript库并没有提供自己关于TypeScript的声明文件,导致TypeScript的使用者无法享受这种库带来的类型,因此社区中就出现了一个项目DefinitelyTyped,他定义了目前市面上绝大多数的JavaScript库的声明,当人们下载JavaScript库相关的@types声明时,就可以享受此库相关的类型定义了
TypeScript有一个叫做声明文件的功能, 即一个以.d.ts为扩展名的文件,通过它我们可以为JavaScript文件定义类型

.d.ts文件。 方法里没有实现细节,它只描述了类型。 TypeScript将.d.ts 和 .js组合到一起,这样,在编译是使用这个声明文件,在运行时使用这个原生的JS文件, 这样我们就可以 使用Ts提供的类型安全和idea的智能语法提示

学习成本

老项目迁移
对于老项目,由于TS兼容ES规范,所以可以比较方便的升级现有的JS(这里指ES6及以上)代码,逐渐的加类型注解,渐进式增强代码健壮性。迁移过程:
-
npm全局安装typescript包,并在工程根目录运行tsc --init,自动产生tsconfig.json文件。
- 默认的3个配置项:更多配置项说明
- "target":"es5": 编译后代码的ES版本,还有es3,es2105等选项。
- "module":"commonjs":编译后代码的模块化组织方式,还有amd,umd,es2015等选项。
- "strict":true:严格校验,包含不能有没意义的any,null校验等选项。
- 默认的3个配置项:更多配置项说明
-
初始化得到的tsconfig.json无需修改,增加"allowJs": true选项。
-
配置webpack配置,增加ts的loader,如awesome-typescript-loader。
loaders: [
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" }
]
此时你可以写文件名为ts和tsx(React)后缀的代码了,它可以和现有的ES6代码共存,VSCode会自动校验这部分代码,webpack打包也没问题了。
- 逐渐的,开始打算重构以前的ES6代码为TS代码,只需将文件后缀改成ts(x)就行,就可以享受TS及IDE智能感知/纠错带来的好处。