typescript
。typescript
why 使用 TS?
提供类型检查(数据约束)
//react 对数据的约束import PropTypes from 'prop-types'component.propTypes = {optionalArray: PropTypes.array,optionalBool: PropTypes.bool,optionalFunc: PropTypes.func,requiredFunc: PropTypes.func.isRequired,}//ts的约束class Component {optionalArray?: Array<string> // string 类型的 数组optionalBool?: boolean // 写上 ? 号,就表示着这个属性可能为空optionalFunc?: (foo: string, bar: number) => boolean // 函数的参数,返回值都一目了然requiredFunc: () => void}使代码更易阅读理解
//不使用tsexport const fetch = function (url, params, user) {// dosomethingreturn http(options).then((data) => {return data}).catch((err) => {return err})}//使用tsexport const fetch = function (url: string | object,params?: any,user?: User): Promise<object | Error> {// dosomethingreturn http(options).then((data) => {return data}).catch((err) => {return err})}
原始数据类型
布尔值
let isDone: boolean = false;
数字
let decLiteral: number = 6;
字符串
let name: string = "bob";
数组
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
泛型
元组
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
访问越界的元素,会使用联合类型替代
let x: [string, number]x = ['hello', 10]x[3] = 'world' // OK, 字符串可以赋值给(string | number)类型 errorx.push()
空值
用
void
表示没有任何返回值的函数function alertName(): void {alert('My name is Tom')}void
类型变量:undefined、nulllet unusable: void = undefined;
Null 和 Undefined
与
void
的区别是,undefined
和null
是所有类型的子类型let u: undefined = undefinedlet n: null = null// 这样不会报错let num: number = undefinednerver
函数 throw 一个 error
永不返回的函数
function infiniteLoop(): never {while (true) {}}nerver 是任何类型的子类型。但是任何类型都不是 nerver 的子类型(包括 any 和它本身)
any
any
类型,允许被赋值为任意类型- 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
- 变量在声明未指定其类型,会被识别为任意值类型
object
类型推论
TS 会在没有明确的指定类型的时候推测出一个类型------类型推论。
let myFavoriteNumber = 'seven'myFavoriteNumber = 7//等价于let myFavoriteNumber: string = 'seven'myFavoriteNumber = 7定义的时候没有赋值,都会被推断成
any
类型而完全不被类型检查
联合类型 |
联合类型: 取值可以为多种类型中的一种
当 TS 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
//error: Property 'length' does not exist on type 'number'.function getLength(something: string | number): number {return something.length}//successfunction getString(something: string | number): string {return something.toString()}
类型断言
语法
<类型>值
值 as 类型
将一个联合类型的变量指定为一个更加具体的类型
function getLength(something: string | number): number {if ((<string>something).length) {return (<string>something).length} else {return something.toString().length}}类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的
对象的类型——接口
- 接口一般首字母大写
- 赋值的时候,变量的形状必须和接口的形状保持一致(不可多属性、少属性)
可选属性
不完全匹配一个形状
interface Person {name: stringage?: number}let tom: Person = {name: 'Tom',}任意属性
[propName: string]: any;
一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
interface Person {name: stringage?: number[propName: string]: string}let tom: Person = {name: 'Tom',age: 25,gender: 'male',}//error 任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性只读属性
用
readonly
定义只读属性只读约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
Q:什么时候使用 readonly,什么时候使用 const?
a: 对象属性使用 readonly,变量使用 const
interface Person {readonly id: numbername: stringage?: number[propName: string]: any}let tom: Person = {id: 89757,name: 'Tom',gender: 'male',}tom.id = 9527//error 使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了数组的类型
数组类型
「类型 + 方括号」表示法
- 数组的项中不允许出现其他的类型
- 数组的一些方法的参数也会根据数组在定义时约定的类型进行限制
let fibonacci: number[] = [1, 1, 2, 3, 5]数组泛型
let fibonacci: Array<number> = [1, 1, 2, 3, 5]任意类型数组
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }]类数组
常见的类数组都有自己的接口定义,如
IArguments
,NodeList
,HTMLCollection
等详见内置对象
函数的类型
函数声明
function sum(x: number, y: number): number {return x + y}- 输入多余的(或者少于要求的)参数,是不被允许的
函数表达式
- 不要混淆了 TS 中的
=>
和 ES6 中的=>
- 在 TS 的类型定义中,
=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。 - 在 ES6 中,
=>
叫做箭头函数
let mySum: (x: number, y: number) => number = function (x: number,y: number): number {return x + y}- 不要混淆了 TS 中的
用接口定义函数的形状
interface SearchFunc {(source: string, subString: string): boolean}let mySearch: SearchFuncmySearch = function (source: string, subString: string) {return source.search(subString) !== -1}可选参数
用
?
表示可选的参数可选参数后面不允许再出现必须参数 (可选参数必须接在必需参数后面 )
function buildName(firstName: string, lastName?: string) {if (lastName) {return firstName + ' ' + lastName} else {return firstName}}
参数默认值
TS 会将添加了默认值的参数识别为可选参数
参数有默认值后,就不受「可选参数必须接在必需参数后面」的限制了
function buildName(firstName: string = 'Tom', lastName: string) {return firstName + ' ' + lastName}
剩余参数
rest 参数只能是最后一个参数
//items 是一个数组。所以我们可以用数组的类型来定义function push(array: any[], ...items: any[]) {items.forEach(function (item) {array.push(item)})}let a = []push(a, 1, 2, 3)
内置对象
ECMAScript 的内置对象
let b: Boolean = new Boolean(1)let e: Error = new Error('Error occurred')let d: Date = new Date()let r: RegExp = /[a-z]/DOM 和 BOM 的内置对象
Document
、HTMLElement
、Event
、NodeList
let body: HTMLElement = document.bodylet allDiv: NodeList = document.querySelectorAll('div')document.addEventListener('click', function (e: MouseEvent) {// Do something})
类型别名
使用
type
创建类型别名
字符串字面量类型
- 字符串字面量类型用来约束取值只能是某几个字符串中的一个
- 类型别名与字符串字面量类型都是使用
type
进行定义
元祖
元组合并了不同类型的对象
直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项
先声明变量,可只赋值部分
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型
let xcatliu: [string, number] = ['Xcat Liu'] //errorlet xcatliu: [string, number]xcatliu = ['Xcat Liu', 25]
枚举
枚举类型用于取值被限定在一定范围内的场景
枚举成员会被赋值为从
0
开始递增的数字,同时也会对枚举值到枚举名进行反向映射enum Days {Sun,Mon,Tue,Wed,Thu,Fri,Sat,}console.log(Days['Sun'] === 0) // trueconsole.log(Days['Mon'] === 1) // trueconsole.log(Days['Tue'] === 2) // trueconsole.log(Days['Sat'] === 6) // trueconsole.log(Days[0] === 'Sun') // trueconsole.log(Days[1] === 'Mon') // trueconsole.log(Days[2] === 'Tue') // trueconsole.log(Days[6] === 'Sat') // true
手动赋值
未手动赋值的枚举项会接着上一个枚举项递增
enum Days {Sun = 7,Mon = 1,Tue,Wed,Thu,Fri,Sat,}console.log(Days['Sun'] === 7) // trueconsole.log(Days['Mon'] === 1) // trueconsole.log(Days['Tue'] === 2) // trueconsole.log(Days['Sat'] === 6) // true注意未手动赋值覆盖手动赋值情况
enum Days {Sun = 3,Mon = 1,Tue,Wed,Thu,Fri,Sat,}console.log(Days['Sun'] === 3) // trueconsole.log(Days['Wed'] === 3) // trueconsole.log(Days[3] === 'Sun') // falseconsole.log(Days[3] === 'Wed') // true手动赋值的枚举项可以不是数字(使用类型断言)
enum Days {Sun = 7,Mon,Tue,Wed,Thu,Fri,Sat = <any>'S',}
常数项和计算所得项
计算所得项
enum Color {Red,Green,Blue = 'blue'.length,}未手动赋值的项不能在计算所得项后面
常数枚举
常数枚举是使用
const enum
定义的枚举类型常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员
const enum Directions {Up,Down,Left,Right,}
外部枚举
- 外部枚举是使用
declare enum
定义的枚举类型 declare
定义的类型只会用于编译时的检查,编译结果中会被删除
- 外部枚举是使用
类
public private 和 protected
public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
的private
修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
修饰的属性或方法是受保护的,它和private
类似,区别是它在子类中也是允许被访问的
抽象类
abstract
用于定义抽象类和其中的抽象方法- 抽象类是不允许被实例化
- 抽象类中的抽象方法必须被子类实现
abstract class Animal {public namepublic constructor(name) {this.name = name}public abstract sayHi()}class Cat extends Animal {public sayHi() {console.log(`Meow, My name is ${this.name}`)}}let cat = new Cat('Tom')类的类型
class Animal {name: stringconstructor(name: string) {this.name = name}sayHi(): string {return `My name is ${this.name}`}}let a: Animal = new Animal('Jack')console.log(a.sayHi()) // My name is Jack
类与接口
接口的用途:
- 接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述。
- 接口可以对类的一部分行为进行抽象
类实现接口
- 一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用
implements
关键字来实现 - 一个类可以实现多个接口
interface Alarm {alert()}interface Light {lightOn()lightOff()}class Car implements Alarm, Light {alert() {console.log('Car alert')}lightOn() {console.log('Car light on')}lightOff() {console.log('Car light off')}}//Car 实现了 Alarm 和 Light 接口,既能报警,也能开关车灯- 一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用
接口继承接口
interface Alarm {alert()}interface LightableAlarm extends Alarm {lightOn()lightOff()}接口继承类
class Point {x: numbery: number}interface Point3d extends Point {z: number}let point3d: Point3d = { x: 1, y: 2, z: 3 }混合类型
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
多个类型参数
定义泛型的时候,可以一次定义多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {return [tuple[1], tuple[0]]}swap([7, 'seven']) // ['seven', 7]泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法 ,可以对泛型进行约束,只允许这个函数传入那些包含
length
属性的变量。这就是泛型约束使用了 extends 约束了泛型 T 必须符合接口 的形状
interface Lengthwise {length: number}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length)return arg}//使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性多个类型参数之间也可以互相约束
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 })//使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段
泛型接口
使用含有泛型的接口来定义函数的形状
//可以把泛型参数提前到接口名上interface CreateArrayFunc<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']泛型类
class GenericNumber<T> {zeroValue: Tadd: (x: T, y: T) => T}let myGenericNumber = new GenericNumber<number>()myGenericNumber.zeroValue = 0myGenericNumber.add = function (x, y) {return x + y}泛型参数的默认类型
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray<T = string>(length: number, value: T): Array<T> {let result: T[] = []for (let i = 0; i < length; i++) {result[i] = value}return result}
声明合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型
函数的合并
function reverse(x: number): numberfunction reverse(x: string): stringfunction reverse(x: number | string): number | string {if (typeof x === 'number') {return Number(x.toString().split('').reverse().join(''))} else if (typeof x === 'string') {return x.split('').reverse().join('')}}接口的合并
接口中的属性在合并时会简单的合并到一个接口
合并的属性的类型必须是唯一的
interface Alarm {price: number}interface Alarm {weight: number}//相当于interface Alarm {price: numberweight: number}若不唯一,类型必须相同,否则会报错
interface Alarm {price: number}interface Alarm {price: number // 虽然重复了,但是类型都是 `number`,所以不会报错weight: number}
接口中方法的合并,与函数的合并一样
interface Alarm {price: numberalert(s: string): string}interface Alarm {weight: numberalert(s: string, n: number): string}//相当于interface Alarm {price: numberweight: numberalert(s: string): stringalert(s: string, n: number): string}
类的合并
类的合并与接口的合并规则一致