今年10月初尤雨溪在 GitHub 发布了 vue3 的 Pre-Alpha 版本源码,同时大部分源码使用了 TypeScript 语言进行编写。能够说 TypeScript 已经成为前端开发将来的趋势。javascript
本篇大部份内容讲 TypeScript 的基础知识,后续内容会更新介绍 TypeScript 在工做中的项目开发及运用。若是您想要获得最新的更新,能够点击下面的连接:html
TypeScript 是一种由微软开发的自由和开源的编程语言,它是 JavaScript 的一个超集,扩展了 JavaScript 的语法。java
经过 npm 安装webpack
$ npm install typescript -g
复制代码
以上命令会在全局环境下安装 tsc
和 tsserver
两个命令,安装完成以后,咱们就能够在任何地方执行它了。git
TypeScript 独立服务器(又名 tsserver )是一个节点可执行文件,它封装了 TypeScript 编译器和语言服务,并经过 JSON 协议公开它们。tsserver 很是适合编辑器和 IDE 支持。es6
通常工做中不经常使用到它。进一步了解tsservergithub
tsc 为 typescript compiler 的缩写,即 TypeScript 编译器,用于将 TS 代码编译为 JS 代码。使用方法以下:web
$ tsc index.ts
复制代码
编译成功后,就会在相同目录下生成一个同名 js 文件,你也能够经过命令参数来修改默认的输出名称。
默认状况下编译器以 ECMAScript 3(ES3)为目标。能够经过 tsc -h
命令查看相关帮助,能够了解更多的配置。
咱们约定使用 TypeScript 编写的文件以 .ts
为后缀,用 TypeScript 编写 React 时,以 .tsx
为后缀。
结合 tsc
命令,咱们一块儿写一个简单的例子。
建立一个 index.ts 文件。
let text: string = 'Hello TypeScript'
复制代码
执行 tsc index.ts
命令,会在同目录下生成 index.js 文件。
var text = 'Hello TypeScript';
复制代码
一个简单的例子就实现完了。咱们能够经过官网提供的 Playground 进行验证。
可是在项目开发过程当中咱们会结合构建工具,如 webpack
,和对应的本地服务 dev-server
等相关工具一同使用。
接下来把咱们了解到的知识结合在一块儿。搭建一个完整的项目。
项目根目录中有一个 tsconfig.json
文件,简单介绍其做用。
若是一个目录下存在一个 tsconfig.json
文件,那么它意味着这个目录是 TypeScript 项目的根目录。tsconfig.json
文件中指定了用来编译这个项目的根文件和编译选项。 一个项目能够经过如下方式之一来编译:
tsc
,编译器会从当前目录开始去查找 tsconfig.json文
件,逐级向上搜索父目录。tsc
,且使用命令行参数 --project
(或 -p
)指定一个包含 tsconfig.json
文件的目录。当命令行上指定了输入文件时,tsconfig.json文件会被忽略。
TypeScript 支持与 JavaScript 几乎相同的数据类型。
String、Number、Boolean、Object(Array、Function)、Symbol、undefined、null
void、any、never、元组、枚举、高级类型
做用:至关于强类型语言中的类型声明
语法:(变量/函数): type
咱们使用 string
表示文本数据类型。 和 JavaScript 同样,可使用双引号 "
或单引号 '
表示字符串, 反引号 ` 来定义多行文本和内嵌表达式。
let str: string = 'abc'
复制代码
和 JavaScript 同样,TypeScript 里的全部数字都是浮点数。这些浮点数的类型是 number
。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量。
let decLiteral: number = 6
let hexLiteral: number = 0xf00d
let binaryLiteral: number = 0b1010
let octalLiteral: number = 0o744
复制代码
咱们使用 boolean
表示布尔类型,表示逻辑值 true
/ false
。
let bool: boolean = true
复制代码
TypeScript 有两种定义数组的方式。 第一种,能够在元素类型后加上 []
。 第二种,可使用数组泛型 Array<元素类型>
。 此外,在元素类型中可使用联合类型。 符号 |
表示或。
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
let arr3: Array<number | string> = [1, 2, 3, 'a']
复制代码
元组类型用来表示已知元素数量和类型的数组,各元素的类型没必要相同,对应位置的类型必须相同。
let tuple: [number, string] = [0, '1']
tuple = ['1', 0] // Error
复制代码
当访问一个已知索引的元素,会获得正确的类型:
tuple[0].toFixed(2)
tuple[1].toFixed(2) // Error: Property 'toFixed' does not exist on type 'string'.
复制代码
能够调用数组 push
方法添加元素,但并不能读取新添加的元素。
tuple.push('a')
console.log(tuple) // [0, "1", "a"]
tuple[2] // Error: Tuple type '[number, string]' of length '2' has no element at index '2'.
复制代码
咱们使用 enum
表示枚举类型。 枚举成员值只读,不可修改。 枚举类型是对 JavaScript 标准数据类型的一个补充。C# 等其它语言同样,使用枚举类型为一组数值赋予友好的命名。
初始值为 0, 逐步递增,也能够自定义初始值,以后根据初始值逐步递增。
enum Role {
Reporter = 1,
Developer,
Maintainer,
Owner,
Guest
}
console.log(Role.Developer) // 2
console.log(Role[2]) // Developer
复制代码
数字枚举会反向映射,能够根据索引值反向得到枚举类型。缘由以下编译后代码所示:
var Role;
(function (Role) {
Role[Role["Reporter"] = 1] = "Reporter";
Role[Role["Developer"] = 2] = "Developer";
Role[Role["Maintainer"] = 3] = "Maintainer";
Role[Role["Owner"] = 4] = "Owner";
Role[Role["Guest"] = 5] = "Guest";
})(Role || (Role = {}));
复制代码
字符串枚举不支持反向映射
enum Message {
Success = '成功',
Fail = '失败'
}
复制代码
在枚举关键字前添加 const
,该常量枚举会在编译阶段被移除。
const enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
复制代码
编译后:
"use strict";
var month = [0 /* Jan */, 1 /* Feb */, 2 /* Mar */]; // [0 /* Jan */, 1 /* Feb */, 2 /* Mar */]
复制代码
外部枚举(Ambient Enums)是使用 declare enum
定义的枚举类型。
declare enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
复制代码
编译后:
"use strict";
let month = [Month.Jan, Month.Feb, Month.Mar];
复制代码
declare
定义的类型只会用于编译时的检查,编译结果中会被删除。因此按照上述例子编译后的结果来看,显然是不能够的。由于 Month 未定义。
declare
和 const
能够同时存在TypeScript 有两种定义对象的方式。 第一种,能够在元素后加上 object
。 第二种,可使用 { key: 元素类型 }
形式。 一样在元素类型中可使用联合类型。注意第一种形式对象元素为只读。
let obj1: object = { x: 1, y: 2 }
obj1.x = 3 // Error: Property 'x' does not exist on type 'object'.
let obj2: { x: number, y: number } = { x: 1, y: 2 }
obj2.x = 3
复制代码
symbol
类型的值是经过 Symbol 构造函数来建立
let s: symbol = Symbol()
复制代码
null
表示对象值缺失,undefined
表示未定义的值。
let un: undefined = undefined
let nu: null = null
复制代码
若其余类型须要被赋值为 null
或 undefined
时, 在 tsconfig.json 中将 scriptNullChecks 设置为 false。或者 使用联合类型。
用于标识方法返回值的类型,表示该方法没有返回值。
function noReturn (): void {
console.log('No return value')
}
复制代码
声明为 any
的变量能够赋予任意类型的值。
let x: any
x = 1
x = 'a'
x = {}
let arr: any[] = [1, 'a', null]
复制代码
咱们先回顾在 JavaScript 中,使用 es6 语法定义一个函数。
let add = (x, y) => x + y
复制代码
上面例子中,add
函数有两个参数 x
和 y
返回其相加之和。 该例子放在 TypeScript 中会提示 参数 x
和 y
隐含一个 any
类型。 因此咱们修改以下:
let add = (x: number, y: number): number => x + y
复制代码
给参数添加 number
类型,在括号以后也添加返回值的类型。这里返回值类型能够省略,由于 TypeScript 有类型推断机制,这个咱们以后详细介绍。
接下来咱们使用 TypeScript 定义一个函数类型并实现它。
let plus: (x: number, y: number) => number
plus = (a, b) => a + b
plus(2, 2) // 2
复制代码
never
类型表示的是那些永不存在的值的类型。 例如,never
类型是那些老是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;变量也多是 never
类型,当它们被永不为真的类型保护所约束时。
never
类型是任何类型的子类型,也能够赋值给任何类型;然而,没有类型是 never
的子类型或能够赋值给 never
类型(除了 never
自己以外)。 即便 any
也不能够赋值给 never
。
let error = (): never => {
throw new Error('error')
}
let endless = (): never => {
while(true) {}
}
复制代码
any
在 TypeScript 中,咱们可使用接口 interface
来定义对象类型。
接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,须要由具体的类去实现,而后第三方就能够经过这组抽象方法调用,让具体的类执行具体的方法。
接下来,定义一个简单的接口:
interface Person {
name: string
age: number
}
let man: Person = {
name: 'James',
age: 30
}
复制代码
咱们定义了一个接口 Person
和变量 man
,变量的类型是 Person
。 这样咱们就约束了该变量的值中对象的 key
和 value
要和接口一致。
须要注意的是:
接口的全部属性可能都不是必需的。
interface Person {
name: string
age?: number
}
let man: Person = {
name: 'James'
}
复制代码
属性名前使用 readonly
关键字制定为只读属性,初始化后不可更改。
interface Person {
readonly name: string
age: number
}
let man: Person = {
name: 'James',
age: 30
}
man.name = 'Tom' // Error: Cannot assign to 'name' because it is a read-only property.
复制代码
用任意的字符串索引,使其能够获得任意的结果。
interface Person {
name: string
age: number
[x: string]: any
}
let man: Person = {
name: 'James',
age: 30,
height: '180cm'
}
复制代码
除了 name
和 age
必须一致之外,其余属性能够随意定义数量不限。
interface Person {
name: string
age: number
[x: string]: string
}
let man: Person = {
name: 'James',
age: 30,
height: '180cm'
}
/** * Type '{ name: string; age: number; height: string; }' is not assignable to type 'Person'. * Property 'age' is incompatible with index signature. * Type 'number' is not assignable to type 'string'. */
复制代码
能够获得任意长度的数组。
interface StringArray {
[i: number]: string
}
let chars: StringArray = ['a', 'b']
复制代码
接口可以描述 JavaScript 中对象拥有的各类各样的外形。 除了描述带有属性的普通对象外,接口也能够描述对象类型和函数类型。
示例以下:
interface List {
readonly id: number
name: string
age?: number
}
interface Result {
data: List[]
}
function render (result: Result) {
console.log(JSON.stringify(result))
}
复制代码
首先咱们定义了一个 List
对象接口,它的内部有 id
、name
和 age
属性。接下来咱们又定义了一个对象接口,这个对象接口有只一个属性 data
,它类型为 List[]
。接下来有一个函数,参数类型为 Result
。
接下来咱们定义一个变量 result
,将它传入 render
函数。
let result = {
data: [
{ id: 1, name: 'A', sex: 'male' },
{ id: 2, name: 'B' }
]
}
render(result)
复制代码
这里须要注意 data
数组内的第一个对象里,增长了一个 sex
属性,可是在上面的接口定义中没有 sex
属性。这时把对象赋给 result
变量,传入函数,不会被编译器检查到。
再看下面的例子:
render({
data: [
{ id: 1, name: 'A', sex: 'male' },
{ id: 2, name: 'B' }
]
})
// Error: Object literal may only specify known properties, and 'sex' does not exist in type 'List'.
复制代码
咱们将对象字面当作参数传给了 render
函数时,编译器会对对象内的属性进行检查。
咱们能够经过类型断言规避这个问题
render({
data: [
{ id: 1, name: 'A', sex: 'male'},
{ id: 2, name: 'B' }
]
} as Result)
复制代码
除了使用 as
关键字,还能够用 <>
符号:
render(<Result>{
data: [
{ id: 1, name: 'A', sex: 'male'},
{ id: 2, name: 'B' }
]
})
复制代码
为了使用接口表示函数类型,咱们须要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每一个参数都须要名字和类型。
在数据类型中咱们提到过,能够用一个变量声明一个函数类型。
let add: (x: number, y: number) => number
复制代码
此外,咱们还能够用接口来定义它。
interface Add {
(x: number, y: number): number
}
let add: Add = (a, b) => a + b
复制代码
除此以外,还有一种更简洁的方式就是使用类型别名
类型别名使用 type
关键字
type Add = (x: number, y: number) => number
let add: Add = (a, b) => a + b
复制代码
interface
定义函数(Add)和用 type
定义函数(Add)有区别?type
和 interface
多数状况下有相同的功能,就是定义类型。 但有一些小区别:
type:不是建立新的类型,只是为一个给定的类型起一个名字。type还能够进行联合、交叉等操做,引用起来更简洁。
interface:建立新的类型,接口之间还能够继承、声明合并。建议优先使用 interface
。
和 JavaScript 同样,TypeScript 函数能够建立有名字的函数或匿名函数,TypeScript 为 JavaScript 函数添加了额外的功能,让咱们能够更容易的使用它。
在基本类型和接口部分中多多少少提到过函数,接下来总结四种定义函数的方式。
function add (x: number, y: number) {
return x + y
}
const add: (x: number, y: number) => number
type add = (x: number, y: number) => number
interface add {
(x: number, y: number) => number
}
复制代码
TypeScript 里的每一个函数参数都是必要的。这里不是指不能把 null
、undefined
当作参数,而是说编译器检查用户是否为每一个参数都传入了值。也就是说,传递给一个函数的参数个数必须与函数指望的参数个数保持一致。咱们举个例子:
function add (x: number, y: number, z: number) {
return x + y
}
add(1, 2) // Error: Expected 3 arguments, but got 2.
复制代码
在上述例子中,函数定义了3个参数,分别为 x
、y
、z
,结果返回 x
和 y
的和。并无使用参数 z
,调用 add
只传入 x
和 y
的值。这时 TypeScript 检查机制提示预期为三个参数,但实际只传入两个参数的错误。如何避免这种状况呢?
在 TypeScript 里咱们能够在参数名旁使用 ?
实现可选参数的功能。
function add (x: number, y: number, z?: number) {
return x + y
}
add(1, 2)
复制代码
通过修改,参数 z
变为可选参数,检查经过。
与 JavaScript 相同,在 TypeScript 里函数参数一样能够设置默认值,用法一致。
function add (x: number, y = 2) {
return x + y
}
复制代码
根据类型推断机制,参数 y
为推断为 number
类型。
与 JavaScript 相同。TypeScript 能够把全部参数收集到一个变量里。
function add (x: number, ...rest: number[]) {
return x + rest.reduce((prev, curr) => prev + curr)
}
add(1, 2, 3) // 6
复制代码
TypeScript 的函数重载,要求咱们先定义一系列名称相同的函数声明。
function add (...rest: number[]): number
function add (...rest: string[]): string
function add (...rest: any[]): any {
let first = rest[0]
let type = typeof first
switch (type) {
case 'number':
return rest.reduce((prev, curr) => prev + curr)
case 'string':
return rest.join('')
}
return null
}
复制代码
上面例子中,咱们定义了三个相同名称的函数,参数分别为 number
、string
、any
类型数组,相继返回的类型与参数类型相同。当调用该函数时,TypeScript 编译器可以选择正确的类型检查。在重载列表里,会从第一个函数开始检查,从上而下,因此咱们使用函数重载时,应该把最容易用到的类型放在最上面。
any
类型函数不是重载列表的一部分传统的 JavaScript 使用函数和基于原型的继承来建立可重用的组件。
function Point (x, y) {
this.x = x
this.y = y
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')'
}
var p = new Point(1, 2)
复制代码
从 ES6 开始,咱们可以使用基于类的面向对象的方式。
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
toString () {
return `(${this.x}, ${this.y})`
}
}
复制代码
TypeScript 除了保留了 ES6 中类的功能之外,还增添了一些新的功能。
class Dog {
constructor (name: string) {
this.name = name
}
name: string
run () {}
}
class Husky extends Dog {
constructor (name: string, color: string) {
super(name)
this.color = color
}
color: string
}
复制代码
上面的例子中须要注意如下几点:
this
的属性以前,必定要调用 super
方法;Dog.prototype
=> {constructor: ƒ, run: ƒ}
,new Dog('huang')
=> {name: "huang"}
TypeScript 可使用三种访问修饰符(Access Modifiers),分别是 public
、private
和 protected
public
修饰的属性或方法是公有的,能够在任何地方被访问到,默认全部的属性和方法都是 public
private
修饰的属性或方法是私有的,不能在声明它的类的外部访问,包括继承它的类也不能够访问
protected
修饰的属性或方法是受保护的,它和 private
相似,区别是它在子类中也是容许被访问
以上三种能够修饰构造函数,默认为 public
,当构造函数为 private
时,该类不容许被继承或实例化;当构造函数为 protected
时,该类只容许被继承。
readonly
修饰的属性为只读属性,只容许出如今属性声明或索引签名中。
公共修饰符
class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
}
let a = new Animal('Jack')
console.log(a.name) // Jack
a.name = 'Tom'
console.log(a.name) // Tom
复制代码
私有修饰符
class Animal {
private name: string
public constructor (name: string) {
this.name = name
}
}
let a = new Animal('Jack')
console.log(a.name) // Error: Property 'name' is private and only accessible within class 'Animal'.
class Cat extends Animal {
constructor (name: string) {
super(name)
console.log(this.name) // Error: // Property 'name' is private and only accessible within class 'Animal'.
}
}
复制代码
须要注意的是,TypeScript 编译以后的代码中,并无限制 private
属性在外部的可访问性。
上面的例子编译后的代码以下:
var Animal = (function () {
function Animal (name) {
this.name = name
}
return Animal
}())
var a = new Animal('Jack')
console.log(a.name)
复制代码
受保护修饰符
class Animal {
protected name: string
public constructor (name: string) {
this.name = name
}
}
class Cat extends Animal {
constructor (name: string) {
super(name)
console.log(this.name)
}
}
复制代码
class Animal {
// public name: string
constructor (public name: string) {
this.name = name
}
}
class Cat extends Animal {
constructor (public name: string) {
super(name)
}
}
let a = new Animal('Jack')
console.log(a.name) // Jack
a.name = 'Tom'
console.log(a.name) // Tom
复制代码
只读修饰符
class Animal {
readonly name: string
public constructor (name: string) {
this.name = name
}
}
let a = new Animal('Jack')
console.log(a.name) // Jack
a.name = 'Tom' //Error: Cannot assign to 'name' because it is a read-only property.
复制代码
注意若是 readonly
和其余访问修饰符同时存在的话,须要写在其后面。
class Animal {
// public readonly name: string
public constructor (public readonly name: string) {
this.name = name
}
}
复制代码
abstract
用于定义抽象类和其中的抽象方法。须要注意如下两点:
抽象类不容许被实例化
abstract class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
}
var a = new Animal('Jack') //Error: Cannot create an instance of an abstract class.
复制代码
抽象类中的抽象方法必须被继承类实现
abstract class Animal {
public name: string;
public constructor (name: string) {
this.name = name;
}
abstract sayHi (): any
}
class Cat extends Animal {
public color: string
sayHi () { console.log(`Hi`) }
constructor (name: string, color: string) {
super(name)
this.color = color
}
}
var a = new Cat('Tom', 'Blue')
复制代码
本章节主要介绍类与接口之间实现、相互继承的操做。
实现(implements)是面向对象中的一个重要概念。通常来说,一个类只能继承自另外一个类,有时候不一样类之间能够有一些共有的特性,这时候就能够把特性提取成接口(interface),用 implements
关键字来实现。这个特性大大提升了面向对象的灵活性。
interface Animal {
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
name: string
eat () {}
}
复制代码
interface Animal {
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
name: string
// eat () {}
}
// Error: Class 'Cat' incorrectly implements interface 'Animal'. Property 'eat' is missing in type 'Cat' but required in type 'Animal'.
复制代码
private
或 protected
。interface Animal {
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
private name: string
eat () {}
}
// Error: Class 'Cat' incorrectly implements interface 'Animal'. Property 'name' is private in type 'Cat' but not in type 'Animal'.
复制代码
interface Animal {
new (name: string): void
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
name: string
eat () {}
}
// Error: Class 'Cat' incorrectly implements interface 'Animal'. Type 'Cat' provides no match for the signature 'new (name: string): void'.
复制代码
实现方法以下:
interface Animal {
name: string
eat (): void
}
interface Predators extends Animal {
run (): void
}
class Cat implements Predators {
constructor (name: string) {
this.name = name
}
name: string
eat () {}
run () {}
}
复制代码
,
分割,同理实现多个接口方式相同。interface Animal {
name: string
eat (): void
}
interface Lovely {
cute: number
}
interface Predators extends Animal, Lovely {
run (): void
}
class Cat implements Predators {
constructor (name: string, cute: number) {
this.name = name
this.cute = cute
}
name: string
cute: number
eat () {}
run () {}
}
复制代码
实现方法以下:
class Auto {
constructor (state: string) {
this.state = state
}
state: string
}
interface AutoInterface extends Auto {}
class C implements AutoInterface {
state = ''
}
复制代码
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1
}
复制代码
一个函数还能够有本身的属性和方法
interface Counter {
(start: number): string
interval: number
reset (): void
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) {}
counter.interval = 123
counter.reset = function () {}
return counter
}
let c = getCounter()
c(10)
c.reset()
c.interval = 5.0
复制代码
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
首先,咱们来实现一个函数 createArray
,它能够建立一个指定长度的数组,同时将每一项都填充一个默认值:
function createArray(length: number, value: any): Array<any> {
let result = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
复制代码
这段代码编译不会报错,可是一个显而易见的缺陷是,它并无准确的定义返回值的类型:
Array<any>
容许数组的每一项都为任意类型。可是咱们预期的是,数组中每一项都应该是输入的 value
的类型。
这时候,泛型就派上用场了:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray<string>(3, 'x') // ['x', 'x', 'x']
复制代码
上例中,咱们在函数名后添加了 <T>
,其中 T
用来指代任意输入的类型,在后面的输入 value: T
和输出 Array<T>
中便可使用了。
接着在调用的时候,能够指定它具体的类型为 string
。固然,也能够不手动指定,而让类型推断自动推算出来:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
复制代码
一样类型数组也能够被类型推断。
function log<T> (value: T): T {
console.log(value)
return value
}
log<string[]>(['a', 'b'])
// or
log(['a', 'b'])
复制代码
定义泛型的时候,能够一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
swap([7, 'seven']) // ['seven', 7]
复制代码
上例中,咱们定义了一个 swap 函数,用来交换输入的元组。
在函数内部使用泛型变量的时候,因为事先不知道它是哪一种类型,因此不能随意的操做它的属性或方法。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length) // Error: Property 'length' does not exist on type 'T'.
return arg
}
复制代码
上例中,泛型 T
不必定包含 length
属性,因此编译的时候会报错。
这时,咱们能够对泛型进行约束,只容许这个函数传入那些包含 length
属性的变量。这就叫泛型约束。
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
复制代码
上例中,咱们使用了 extends
约束了泛型 T
必须符合接口 Lengthwise
的形状,也就是必须包含 length
属性。
此时若是调用 loggingIdentity
函数的时候,传入的参数不包含 length
,那么在编译阶段就会报错了。
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
loggingIdentity(7) // Error: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
复制代码
多个类型参数之间也能够相互约束。
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id]
}
return target
}
let x = { a: 1, b: 2, c: 3, d: 4 }
copyFields(x, { b: 10, d: 20 }) // { a: 1, b: 10, c: 3, d: 20 }
复制代码
上述例子中,咱们使用了两个类型参数,其中要求 T
继承 U
,这样就保证了 U
上不会出现 T
中不存在的字段。
能够用泛型来约束函数的参数和返回值类型。
type Log = <T>(value: T) => T
let log: Log = (value) => {
console.log(value)
return value
}
log<number>(2) // 2
log('2') // '2'
log(true) // <boolean>true
复制代码
以前学习过,可使用接口的方式来定义一个函数须要符合的形状。
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function (source: string, subString: string) {
return source.search(subString) !== -1
}
复制代码
一样也可使用含有泛型的接口来定义函数的形状。
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>
}
let createArray: CreateArrayFunc
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
复制代码
进一步,咱们能够把泛型参数提早到接口名上。
interface CreateArrayFunc<T> {
<T>(length: number, value: T): Array<T>
}
let createArray: CreateArrayFunc<any>
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
复制代码
注意,此时在使用泛型接口的时候,须要定义泛型的类型。
若不想在使用泛型接口时定义泛型的类型,那么,须要在接口名上的泛型参数设置默认类型。
interface CreateArrayFunc<T = any> {
<T>(length: number, value: T): Array<T>
}
let createArray: CreateArrayFunc
复制代码
与泛型接口相似,泛型也能够用于类的类型定义中。
class Log<T> {
run (value: T) {
console.log(value)
return value
}
}
let log1 = new Log<number>()
log1.run(1) // 1
let log2 = new Log()
log2.run('1') // '1'
复制代码
class Log<T> {
static run (value: T) {
console.log(value)
return value
}
}
// Error: Static members cannot reference class type parameters.
复制代码
TypeScript 编译器在作类型检查时,所秉承的一些原则,以及表现出的一些行为。
本章节分为三大部分:类型推断、类型兼容性、类型保护。
不须要指定变化的类型(函数的返回值类型),TypeScript 能够根据某些规则自动为其推断出一个类型。
基本类型推断常常出如今初始化变量的时候。
let a
// let a: any
let a = 1
// let a: number
let a = []
// let a: any[]
复制代码
声明变量 a
时,咱们不指定它的类型,ts
就会默认推断出它是 any
类型。
若是咱们将它复制为 1
,ts
就会推断出它是 number
类型。
若是咱们将它复制为 []
,ts
就会推断出它是 any
类型的数组。
基本类型推断还会出如今定义函数参数。
let a = (x = 1) => {}
// let a: (x?: number) => void
复制代码
声明函数 a
,设置一个参数 x
,为它赋值一个默认参数 1
,此时 ts
就会推断出它是 number
类型。一样返回值类型也会被推断。
当须要从多个类型中推断出一个类型时,ts
就会尽量的推断出一个最佳通用类型。
let a = [1, null]
// let a: (number | null)[]
复制代码
声明一个变量 a
,值为一个包含数字 1
和 null
的数组。此时,变量 a
就被推断为 number
和 null
的联合类型。
以上的类型推断都是从右向左的推断,根据表达式的值推断出变量的类型。还有一种方式是从左到右,根据上下文推断。
一般发生在事件处理中。
window.onkeydown = (event) => {
}
// (parameter) event: KeyboardEvent
复制代码
为 window
绑定 onkeydown
事件,参数为 event
,此时 ts
会根据左侧的事件绑定推断出右侧事件的类型。
当一个类型 Y 能够赋值给另外一个类型 X 时,咱们能够认为类型 X 兼容类型 Y。
X 兼容 Y : X (目标类型) = Y (源类型)
let s: string = 'abc'
s = null
复制代码
默认会提示 Type 'null' is not assignable to type 'string'. 若是将 tsconfig.json
内的 strictNullChecks
的值设置为 false
,这时编译就不会报错。
能够说明 string
类型兼容 null
类型,null
是 string
类型的子类型。
示例以下:
interface X {
a: any
b: any
}
interface Y {
a: any
b: any
c: any
}
let x: X = { a: 1, b: 2 }
let y: Y = { a: 1, b: 2, c: 3 }
x = y
y = x // Error: Property 'c' is missing in type 'X' but required in type 'Y'.
复制代码
y
能够赋值给 x
,x
不能够赋值给 y
。
示例以下:
type Handler = (a: number, b: number) => void
function hof(handler: Handler) {
return handler
}
let handler1 = (a: number) => {}
hof(handler1)
let handler2 = (a: number, b: number, c: number) => {}
hof(handler2)
// Error: Argument of type '(a: number, b: number, c: number) => void' is not assignable to parameter of type 'Handler'.
let handler3 = (a: string) => {}
hof(handler3)
// Error: Types of parameters 'a' and 'a' are incompatible. Type 'number' is not assignable to type 'string'.
复制代码
上述示例中,目标类型 handler
有两个参数,定义了三个不一样的函数进行测试。
handler1
函数只有一个参数,将 handler1
传入 hof
方法做为参数(兼容)handler2
函数有三个参数,一样做为参数传入 hof
方法(不兼容)。handler2
函数参数类型与目标函数参数类型不一样(不兼容)示例以下:
// 固定参数
let a = (p1: number, p2: number) => {}
// 可选参数
let b = (p1?: number, p2?: number) => {}
// 剩余参数
let c = (...args: number[]) => {}
a = b
a = c
b = a // Error
b = c // Error
c = a
c = b
复制代码
tsconfig.json
内的 strictFunctionTypes
的值设置为 false
,这时编译就不会报错。剩余参数兼容固定参数和可选参数。示例以下:
interface Point3D {
x: number
y: number
z: number
}
interface Point2D {
x: number
y: number
}
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}
p3d = p2d
p2d = p3d // Error: Property 'z' is missing in type 'Point2D' but required in type 'Point3D'.
复制代码
若是想要上述示例中的 p2d = p3d 兼容。将 tsconfig.json
内的 strictFunctionTypes
的值设置为 false
。
示例以下:
let f = () => ({ name: 'Alice' })
let g = () => ({ name: 'Alice', location: 'Beijing' })
f = g
g = f // Error
复制代码
在函数部分中有介绍函数重载,这里咱们重温一下。
function overload (a: number, b: number): number function overload (a: string, b: string): string function overload (a: any, b: any): any {} 复制代码
函数重载分为两个部分,第一个部分为函数重载的列表,也就是第1、二个 overload
函数,也就是目标函数。第二个部分就是函数的具体实现,也就是第三个 overload
函数,也就是源函数。
示例以下:
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
let fruit: Fruit.Apple = 3
let no: number = Fruit.Apple
let color: Color.Red = Fruit.Apple // Error
复制代码
示例以下:
class A {
constructor (p: number, q: number) {}
id: number = 1
}
class B {
static s = 1
constructor (p: number) {}
id: number = 2
}
let aa = new A(1, 2)
let bb = new B(1)
aa = bb
bb = aa
复制代码
示例以下:
interface Empty<T> {}
let obj1: Empty<number> = {}
let obj2: Empty<String> = {}
obj1 = obj2
// 设置属性
interface Empty<T> {
value: T
}
let obj1: Empty<number> = { value: 1 }
let obj2: Empty<String> = { value: 'a'}
obj1 = obj2 // Error
复制代码
obj1
与 obj2
相互兼容,若此时 Empty
设置了属性 value: T
时,obj1
与 obj2
不兼容。泛型函数
let log1 = <T>(x: T): T => {
console.log('x')
return x
}
let log2 = <U>(y: U): U => {
console.log('y')
return y
}
log1 = log2
复制代码
TypeScript
可以在特定的区块中保证变量属于某种肯定的类型。
能够再此区块中放心地引用此类型的属性,或者调用此类型的方法。
enum Type { Strong, Week }
class Java {
helloJava () {
console.log('hello java')
}
java: any
}
class JavaScript {
helloJavaScript () {
console.log('hellp javascript')
}
javascript: any
}
function getLanguage (type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
if (lang.helloJava) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
return lang
}
getLanguage(Type.Strong)
复制代码
定义 getLanuage
函数参数 type
,判断 type
为强类型时,返回 Java
实例,反之返回 JavaScript
实例。
判断 lang
是否有 helloJava
方法,有则执行该方法,反之执行 JavaScript
方法。此时这里有一个错误 Property 'helloJava' does not exist on type 'Java | JavaScript'.
。
解决这个错误,咱们须要给 lang
添加类型断言。
if ((lang as Java).helloJava) {
(lang as Java).helloJava()
} else {
(lang as JavaScript).helloJavaScript()
}
复制代码
这显然不是很是理想的解决方案,代码可读性不好。咱们能够利用类型保护机制,以下几个方法。
判断实例是否属于某个类
if (lang instanceof Java) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
复制代码
判断一个属性是否属于某个对象
if ('java' in lang) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
复制代码
判断一个基本类型
if (typeof x === 'string') {
x.length
} else {
x.toFixed(2)
}
复制代码
function isJava(lang: Java | JavaScript): lang is Java {
return (lang as Java).helloJava !== undefined
}
if (isJava(lang)) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
复制代码
介绍五种 TypeScript 高级类型:交叉类型、联合类型、索引类型、映射类型、条件类型。
这些类型在前面多多少少有被提到过,咱们在统一梳理一遍。
&
符号,多个类型合并为一个类型,新的类型具备全部类型的特性。
interface DogInterface {
run (): void
}
interface CatInterface {
jump (): void
}
let pet: DogInterface & CatInterface = {
run () {},
jump () {}
}
复制代码
取值能够为多种类型中的一种
let a: number | string = 1 // or '1'
复制代码
字面量联合类型
let a: 'a' | 'b' | 'c'
let b: 1 | 2 | 3
复制代码
对象联合类型
interface DogInterface {
run (): void
}
interface CatInterface {
jump (): void
}
class Dog implements DogInterface {
run () {}
eat () {}
}
class Cat implements CatInterface {
jump () {}
eat () {}
}
enum Master { Boy, Girl }
function getPet (master: Master) {
let pet = master === Master.Boy ? new Dog() : new Cat()
pet.eat()
return pet
}
复制代码
getPet
方法体内的 pet
变量被推断为 Dog
和 Cat
的联合类型。在类型未肯定的状况下,只能访问联合类型的公有成员 eat
方法。
let obj = {
a: 1,
b: 2,
c: 3
}
function getValues (obj: any, keys: string[]) {
return keys.map(key => obj[key])
}
getValues(obj, ['a', 'b']) // [1, 2]
getValues(obj, ['d', 'e']) // [undefined, undefined]
复制代码
当 keys
传入非 obj
中的属性时,会返回 undefined
。如何进行约束呢?这里就须要索引类型。
索引类型的查询操做符 keyof T
表示类型 T 的全部公共属性的字面量联合类型
interface Obj {
a: number
b: string
}
let key: keyof Obj // let key: "a" | "b"
复制代码
索引访问操做符 T[K]
对象 T 的属性 K 表明的类型
let value: Obj['a'] // let value: number
复制代码
泛型约束 T extends U
let obj = {
a: 1,
b: 2,
c: 3
}
function getValues <T, U extends keyof T>(obj: T, keys: U[]): T[U][] {
return keys.map(key => obj[key])
}
getValues(obj, ['a', 'b']) // [1, 2]
getValues(obj, ['d', 'e']) // Type 'string' is not assignable to type '"a" | "b" | "c"'.
复制代码
能够讲一个旧的类型生成一个新的类型,好比把一个类型中的全部属性设置成只读。
interface Obj {
a: string
b: number
c: boolean
}
// 接口全部属性设置成只读
type ReadonlyObj = Readonly<Obj>
// 源码
/** * Make all properties in T readonly */
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 接口全部属性设置成可选
type PartialObj = Partial<Obj>
// 源码
/** * Make all properties in T optional */
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 抽取Obj子集
type PickObj = Pick<Obj, 'a' | 'b'>
// 源码
/** * From T, pick a set of properties whose keys are in the union K */
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type RecordObj = Record<'x' | 'y' , Obj>
复制代码
ts
还有更多内置的映射类型,路径在 typescript/lib/lib.es5.d.ts
内提供参考。
形式为 T extends U ? X : Y
,若是类型 T
能够赋值为 U
结果就为 X
反之为 Y
。
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
'object'
type T1 = TypeName<string> // type T1 = "string"
type T2 = TypeName<string[]> // type T2 = "object"
复制代码
若 (A | B) extends U ? X : Y
形式,其约等于 (A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | number> // type T3 = "string" | "number"
复制代码
利用该特性可实现类型过滤。
type Diff<T, U> = T extends U ? never : T
type T4 = Diff<'a' | 'b', 'a'> // type T4 = "b"
// 拆解
// Diff<'a', 'a'> | Diff<'b', 'a'>
// never | 'b'
// 'b'
复制代码
根据 Diff
再作拓展。
type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null> // type T5 = string | number
复制代码
以上 Diff
和 NotNull
条件类型官方已经实现了。
Exclude<T, U>
等于 Diff<T, U>
NonNullable<T>
等于 NotNull<T>
还有更多的官方提供的条件类型,可供你们参考。
// Extract<T, U>
type T6 = Extract<'a', 'a' | 'b'> // type T6 = "a"
// ReturnType<T>
type T7 = ReturnType<() => string> // type T7 = string
复制代码