TypeScript基础教程
TypeScript
类型
typescript提供类型支持,typescript不仅支持原有的JavaScript类型,例如字符串、数字、数组等,还支持一些新的类型,例如元组、void、枚举等。
常用类型
数组与元组
数组有两种方式定义元素类型
复制 // 两种方式等价
const arr1:Array<string | number> = ['123','321',111]
const arr1:(string | number)[] = ['123','321',111]
元组与数组类似,但是元组更加严格,元组适用于已知所有元素个数和类型的情况下。
复制 let tuple1:[number,string] = [1,'2'] // true
let tuple2:[number,string] = ['1',2] // false
let tuple1:[number,string] = [1,'2'] // false
// 数组长度、元素类型、元素位置必须对应
void与never
void通常用于表示函数没有显式返回值,或者值为null
或undefined
的变量。
never表示永不存在的值,常见与总是报错或根本不会返回的函数。
复制 // 不返回值的函数
function fn():void{ }
let a: void = null;
let b:void = undefined;
复制 // 抛出错误和永不到达终点的场景使用never
function fn(): never{
throw new Error()
}
function fn(): never{
while (true) { }
}
any和unknown
any
可以是任意类型,这将跳过类型检查
unknown
与 any
类似,它们的不同之处在于,虽然它们都可以是任何类型,但是当 unknown
类型被确定是某个类型之前,它不能被进行任何操作比如实例化、getter
、函数执行等等
复制 function fn(arg: unknown): void{
// error
console.log(arg.length)
// true
if (typeof arg === 'string') {
console.log(arg)
}
}
如果你事先不确定需要用哪个类型,你可以先定义any
类型或者unknown
,之后再进行类型判断,但是any
会跳过类型检查,因此建议使用unknown
类型断言
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息,你可以手动告诉ts值的类型,ts则会相信你并将你给定的类型当做该值的类型。 类型断言有两种方式,尖括号或者使用as
复制 let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
一般情况下两种方式都可以,但是在jsx中只有as才被运行,因此建议统一使用as
语法。
接口
基础
接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。例如定义一个对象所有属性的类型。
复制 // 规定必须包含number类型的count、string类型的str
interface Ob{
count: number
str: string
}
const obj: Ob = {
count: 100,
str: '123'
}
// 类似于
const obj:{count: number,str: string} = {
count: 100,
str: '123'
}
const obj: Ob
表示obj
变量需要实现接口Ob
所定义的属性和方法。
可选、只读
接口里的属性和方法并非全部都需要实现,假如你想定义一个可选的属性,只需要运用?:
即可。
readonly
则表示该属性一旦声明后就不能再被改变。
复制 interface Ob{
count?: number // 可选
readonly str: string // 只读,声明后不可更改
}
函数类型
接口不仅仅用来定义对象类型,也可以用来定义函数类型。为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
复制 interface Fn{
(arg:string): void
}
const fn:Fn = function (a){
}
fn(123) // Error
函数参数名称可以和接口参数名称不同,但是类型必须相同。
方法类型
接口除了定义对象属性,也可以定义对象方法
复制 interface Obj{ fn: (q:string)=> void}
const o:Obj = { fn(q:string){}}
可索引的类型
可索引类型具有一个 索引签名 ,它描述了对象索引的类型,还有相应的索引返回值类型。
复制 interface Ob{
[name: number]: string
}
let ob: Ob = {
123: 'world',
1234: '123',
'1': 123 // Error
}
[name: number]: string
表示对象的key只能是number类型,值是string的属性,其中name只是为了可读性,没有实际意义,属性名可以不为name。
另外索引类型仅可以使用number和string。
复制 interface Ob{
[name: number]: string
}
let ob: Ob = {
'123': 'world',
'1234': '123',
}
ob['1'] = 111 // true
ob[2] = 123 // false
索引签名[name: number]: string
并没有限制必须要是number类型的key,它限制的是:所有number类型的key所对应的值必须是string。
继承接口
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
复制 interface A{
a: string
}
interface B extends A {
b: string
}
const s: B = {
a: '123',
b: '321'
}
合并接口
合并接口非常简单,当声明了多个同名的接口时,ts就会自动合并该接口
复制 interface A{
a: string
}
interface A{
b: string
}
const a:A = {
a: '1',
b: '2'
}
字面量类型
字符串、数字和布尔值这三个类型可以使用字面量类型,通常是在定义常量的时候使用
复制 const a: '123' = '123' // true
const b: '123' = '1234' // false
const a: '123' | 123 = '123' // true
除了直接使用字面量外,也可以使用类型别名来实现相同的效果。
复制 // 通过type关键字来声明一个类型别名
type ABC = 'abc' // 声明ABC类型,并且值只能是‘abc’
const c:ABC = 'abc'
类型别名
实现类型别名
运用type关键字,创建类型别名
复制 type Length = { length: number } // 对象
type Fn = <T>(arg: T) => T // 泛型
type strNum = string | number // 联合类型
type allSelect = '1' | '2' | '3' // 字符串字面量类型
const len: Length = { length: 100 }
const fn: Fn = function (arg) { return arg }
const str:strNum = '123'
const selectOne = '1'
const selectTwo = '4'
type能做的不只是上面所写的那些,你可以使用类型别名为任何类型指定名称,事实上,type和interface很相似。 ### 合并type 你可以通过&
字符来合并type
复制 type A = {
a: string;
}
type B = {
b: number
}
type C = A & B
type D = C & {
c: boolean
}
const d:D = {a: '1',b: 2,c: true}
类型别名和接口之间的差异
类型别名和接口非常相似,在许多情况下,您可以自由选择它们。几乎接口的所有功能都可以用type实现,关键的区别在于类型别名不能被重新打开来添加新的属性,而接口总是可扩展的。
复制 // type
type A = {
a: string;
}
type B = {
b: number
}
type C = A & B
// interface可以使用继承和合并
// 继承
interface Animal {
name: string
}
interface Cat extends Animal {
honey: boolean
}
// 合并
interface Window {
title: string
}
interface Window {
ts: TypeScriptAPI
}
函数
函数类型
直接在函数声明时定义参数类型和返回值类型。
复制 function a(x: number, y: number): number {
return x + y;
}
let b = function(x: number, y: number): number { return x + y; };
let c = (arg: string): void => { };
书写完整的函数类型
复制 const a: (arg: string) => void = () => { }
a(123) // error
a('123') // true
通常将完整的函数类型写到接口里,以达到复用的目的。
复制 interface A{
(arg: string): string
}
interface B{
():void
}
const a: A = (arg) => { return '123' } // true
const b: A = function(arg) { return 123 } // false
const c: B = function(){} // true
可选参数、默认参数与剩余参数
可选参数 用?:
来定义一个可选参数,注意可选参数一定要在必填参数后面。
复制 function fn(a: string, b?: number) {
}
fn('123') // true
fn('123', '321') // error
默认参数
复制 function fn(a: string, b='123') {
}
fn('123') // true
fn('123', '321') // true
fn('123', 123) // error
默认参数也会被认为是可选参数,并且typescript会对默认值进行类型推导,因此传入对默认参数类型必须和默认值相同,当然你也可以手动指定默认参数的类型。
复制 function fn(a: string, b:string|number='123') {
}
fn('123')
fn('123', '321')
fn('123', 123)
剩余参数
复制 function fn(a: string, ...args:number[]) {
fn('123') // true
fn('123', '321') // false
fn('123', 123) // true
箭头函数
ES6新增了箭头函数,你也可以在ts中为箭头函数定义参数和返回值的类型。
复制 const fn: (q:string)=> string = (q)=>q
可实例化
可实例化仅仅是可调用的一种特殊情况,它使用 new
作为前缀。它意味着你需要使用 new
关键字去调用它:
复制 interface NewFn {
new (): string;
}
// 使用
declare const Foo: NewFn;
const bar = new Foo(); // bar 被推断为 string 类型
函数重载
TypeScript中的函数重载可以定义多个函数声明,其中函数的参数必须不同,类型可以相同也可以不同。当我们调用重载函数时,TypeScript会根据传入参数的类型和个数来决定调用哪个函数,从而使函数可以返回不同的结果。
下面是一个简单的函数重载的例子:
复制 function getInfo(name: string): string;
function getInfo(age: number): number;
function getInfo(info: any): any {
if (typeof info === 'string') {
return info.length;
} else if (typeof info === 'number') {
return info + 10;
}
}
let nameLen: string = getInfo('Tom');
let agePlus: number = getInfo(20);
console.log(nameLen); // 3
console.log(agePlus); // 30
泛型
泛型变量
某些时候我们会多次使用到某个类型,但是我们事先却又不知道它具体是哪个类型,例如一个函数接受某个类型的参数,并且需要返回相同类型的值。
如果事先确定参数类型,那么就很容易直接指定类型,但是假如它并不特定是某个类型,它可能是泛指一些类型,那么这个时候就可以使用泛型。
复制 // 只能特定某一类型,并且需要提前知道是哪种类型
function fn(arg:string):string{
return arg
}
// 丢失了参数与返回值同类型这一信息
function fn(arg:any):any{
return arg
}
// 泛型,fn的参数和返回值必须是同一类型,并且arg可以是任意类型
function fn<O>(arg: O):O{
return arg
}
泛型在使用上可以指定类型或者不指定(ts进行类型推导)
复制 // 推荐第一种,某些复杂的情况需要手动指定(第二种)
console.log(fn('123'))
console.log(fn<string>('123'))
泛型接口
复制 interface GenericIdentityFn {
<T>(arg: T): T;
}
泛型类
泛型类看上去与泛型接口差不多。 泛型类使用( <>
)括起泛型类型,跟在类名后面。
复制 class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
泛型约束
有时候需要约束泛型变量,例如要求arg必须包含num属性,此时可以使用泛型约束
复制 interface Num{
num: number
}
function fn<T extends Num>(arg: T):T{
return arg
}
console.log(fn('123')) // Error
console.log(fn({num:123}))
枚举
数字枚举
复制 enum InputType{
File,
Text
}
console.log(InputType.File)
console.log(InputType[InputType.File],InputType[0])
// 0
// Fild,Fild
数字枚举既可以正向映射,也可以反向映射。
enum默认是数字枚举,这类似于
复制 enum InputType{
File,
Text
}
// 类似于
enum InputType{
File = 0,
Text = 1
}
也可以显式地指定成员的值,后面没有指定的值则自动递增。
复制 enum InputType{
File = 2,
Text
}
console.log(InputType.Text)
// 3
字符串枚举
字符串枚举就是使用字符串作为枚举成员的值
复制 enum InputType{
File = 'file',
Text = 'text'
}
console.log(InputType.File)
console.log(InputType['file'])
// file
// undefined
字符串枚举不支持反向映射
const枚举
常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除,这在对性能要求很高的场景下会很有用。
复制 const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
// 等价于
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
联合枚举类型
枚举可以当作联合类型。
复制 enum InputType{
File ,
Text
}
const a: InputType[] = [InputType.File, InputType.Text]
let b: InputType = InputType.File
// 等价于
let b: InputType.File | InputType.Text = InputType.File
异构枚举
ts允许混合数字枚举和字符串枚举,但是一般情况很少使用。
复制 enum BooleanLikeHeterogeneousEnum { No = 0, Yes = "YES", }
可选链
ES6中新增了可选链。
复制 const o = {fn: null}
const a = []
console.log(o?.a) // 属性
console.log(o.fn?.()) // 函数
console.log(a?.[0]) // 数组
// 以上都返回 undefined
在一些情况下,你可能确定某个属性存在,但是ts可能会假设它不存在,这个时候你可以使用非空断言运算符。
复制 function fn(x?: number | null) {
// No error
console.log(x!.toString());
}
当然在这种情况下另一种的方法是进行检查
复制 function fn(x?: number | null) {
// No error
if(x){
console.log(x!.toString());
}
}
类型判断(类型范围缩小)
复制 function fn1(a: string|number){
console.log(a.padStart(10,'0'))
}
在上面代码中,padStart
是字符串特有的方法,但是ts认定参数a
是一个字符串和数字的联合类型,因此会引发报错,对于这种情况我们需要缩小类型范围,让ts类型系统认定参数a
是一个字符串。 解决的方法也很简单,就是将类型范围缩小至允许的类型。下面谈谈几种常见的方法。 ### typeof 对多大多数原始类型(null除外)和一些引用类型(例如function类型)来说,使用typeof
来缩小类型范围是一个很好的选择。
复制 function fn2(a: string|number){
if(typeof a === 'string'){
console.log(a.padStart(10,'0'))
}
}
真值
复制 if(a){
// 过滤null、undefined、""、NaN、0、0n
}
if(a != null){
// 过滤null、undefined
}
严格相等
如果一个大范围的类型严格等于(===
)某个小范围的值,那么就能将类型范围缩小。
复制 function fn3(a: string|number){
let b:string = '123';
if(a===b){
// a是字符串,且值是'123'
console.log(a.padStart(10,'0'))
}
}
这是借助了严格相等需要类型也相等的特性,如果把严格相等换成普通相等(==
)则无法实现效果。 ### in 借助in
关键字,可以判断某个属性是否存在对象中。
复制 const p = Object.create({pro: 123})
p.a = 123
console.log('a' in p)
console.log('pro' in p)
需要注意的是in
关键字不仅会检查实例属性,还会检查原型对象上的属性,当属性存在于对象的原型对象中时也会返回true
。
instanceof
instanceof
可以用来检查引用类型,语法是实例 instanceof 构造函数
,但是要注意它的原理是遍历左侧实例的原型链,找到与右侧构造函数相同的原型对象时返回true
,也就是说以下几种语句都返回true
复制 [] instanceof Array // true
[] instanceof Object // true
new Date() instanceof Object // true
构造函数的静态方法
基于instanceof
的缺陷,ES6在一些构造函数上增加了检测类型的静态方法,例如Array.isArray
和Number.isNaN
。
参考
TypeScript: Documentation - The Basics (typescriptlang.org)