1.概述
TypeScript(TS)是JavaScript(JS)的超集,它只是在JS的基础上加入的TS的类型系统,也就是说,我们已经编写好的历史JS脚本在切换到TS时,可以不经过任何修改也能正常运行。
同时,TS的类型系统会将我们编写的脚本中不符合语法的部分标记出来,让实际运行时会出现的异常、错误或者一些意想不到的情况提前暴露出来,减少运行时bug出现的概率。
本文中的部分内容来自于《TypeScript官方文档》,由于本文只是简单的快速上手,想要查看更详细的问题,可以去官网文档进行查看。
此外,TypeScript官网还提供了在线编辑的页面,《TypeScript:Playground》可以在这个页面编辑并测试TS脚本。
2.TS中的类型
TypeScript的基本类型是由JavaScript中的类型:boolean
, bigint
, null
, number
, string
, symbol
, undefined
加上新增的any
,never
,null
组成。
2.1.类型推断
对于一个不确定类型的变量,我们可以通过typeof
获取到变量的类型,例如:
const num = 1;
const str = 'Hello TS!';
console.log(typeof num);
console.log(typeof str);
会打印出,“number"和"string”,也就是说即使没有显式的定义变量的类型,,TS也会通过类型推断由变量值来推导变量的类型。
2.2.类型定义
在某些函数中,我们需要获取到变量的类型才能做下一步操作,就可以使用typeof
来进行判断,以一个简单的函数为例:
const plus = (x , y) => {
if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
throw new Error('请输入数字');
}
console.log(plus(1 , 2);
// 如果没有使用typeof,则会打印'12',这与函数的初衷不符
console.log(plus(1 , '2');
使用TS时,我们只需要显式的定义参数的类型,即可在编码阶段就获取到错误提示。
上图中的number
就是TS的核心语法,定义变量的类型,接下来列举一些常见的定义变量类型的方式。
基础变量类型
定义变量的类型,就是在给变量加上一个限制,只有满足这个限制,才能够正常的赋值。
let num: number = 1;
let bool: boolean = false;
let str: string = 'Hello';
// any类型可以表示可以赋值为任意类型
let anything: any = 2;
anything = true;
anything = '任意类型';
......
接口定义变量类型
当我们需要使用到对象,并对对象中的字段进行限制时,就可以使用接口来限制变量的类型,例如:
interface User {
name: string,
age: number,
nickname?: string
}
在下列的示例中,通过User接口来定义对象,并且只初始化了name字段,就会提示我们缺少了age字段的初始化操作:
但是没有提示我们缺少nickname
,这是因为 ?:
表示的是可选属性,可以不填。
注:interface中除了使用基础的变量类型限制外,接口中的字段也可以用其他接口来做限制,例如:
interface System {
user: User,
name: string
}
函数形参变量类型
与接口同理,在函数的形参中,我们也可以使用?:
来表示可选参数。
const printParam = (p1: string , p2?: string) => console.log(`${p1},${p2}`);
printParam('Hello','world');
printParam('Hello');
两种调用方式,都不会提示语法错误。
2.3.复杂类型
对于简单类型来说,限制字段的值只能是同一种类型,而复杂类型就是限制字段可以是多种类型中的其中一个。
我们可以通过两种形式来对复杂类型进行定义:联合,泛型。
2.3.1.联合
使用联合类型的方式很简单,在多个类型之间使用|
进行连接,使用或的语义即可,比如我们希望一个字段既可以是数字,又可以是字符串:
let multi: number | string = 1;
multi = 'abc';
用一个更加详细的例子来说明联合的作用,假如现在有一个获取对象中的字段值的函数,需要限制传入的key
是对象中的字段,我们除了可以限制变量的基础数据类型以外,还可以限制变量只能被赋值为一些具体的值,例如下面例子中的形参key
。
interface User {
name: string,
age: number,
nickname?: string
}
let user:User = {
name :'张三',
age: 18,
nickname: '翼德'
}
// 此处 key 的值只能是 name,age,nickname中的其中一个,如果不是,则会提示错误
const getProperty = (obj:User , key:'name'|'age'|'nickname'): any => {
return obj[key];
}
现在我们对User进行修改,加入一个性别字段gender
,可以想象到的是,我们也需要同时在key
后面加入gender
字段才行,这种情况,我们可以用keyof
来更灵活的处理,下面我们把getProperty()函数做一点修改。
interface User {
name: string,
age: number,
nickname?: string,
gender: number
}
let user:User = {
name :'张三',
age: 18,
nickname: '翼德',
gender: 0
}
// keyof User 等价于 'name'|'age'|'nickname'|'gender'
const getProperty = (obj:User , key:keyof User): any => {
return obj[key];
}
let myName1:string = getProperty(user,'gender');
使用keyof
之后,无论对象中的字段如何增减,我们都不需要再修改函数形参的类型了。
2.3.2.泛型
TS中的泛型大多数情况是使用在函数上(也可以使用在类上),我们可以把泛型理解为一种特殊的参数,在调用函数的时候,可以显式或隐式的传入类型。
以identity
函数为例,我们定义一个简单的函数,传入什么参数就返回什么参数。
function identity(arg: any): any {
return arg;
}
let ident:string = identity(123);
因为返回值的类型是any
,所以对于程序来说,并不能明确的知道返回值的类型到底是什么,所以当我们把ident
的类型定义为string
时,编译器也不会提示我们异常。
想要明确返回值的类型与传入的参数值类型一致,可以使用泛型来处理,对上面的代码做一点的修改:
function identity<T> (arg: T): T {
return arg;
}
let ident:string = identity<number>(123);
在identity
后使用<>
来定义类型的形参,调用时同样使用<>
传入实际的类型,与上面定义的any
类型不同,此时编译器会提醒我们出现了类型转换异常。
除了显示的调用以外,更常用的方式是在调用时不传入类型参数,使用类型推断来隐式的传入参数类型:
// 等价于 let ident:number = identity<number>(123);
let ident:number = identify(123);
以上面2.3.1中的函数为例,我们可以使用泛型来更加灵活的处理参数与返回值之间的关系。
interface User {
name: string,
age: number,
nickname?: string
}
let user:User = {
name :'张三',
age: 18,
nickname: '翼德'
}
// K extends keyof T,表示将K泛型的范围限制在T对象的字段名范围内
const getProperty = <T,K extends keyof T>(obj:T , key:K): T[K] => {
return obj[key];
}
将T[K]
代入到上面的User
中,如果传入的key
等于name
,则返回值T[K]
类型为string
,同理,传入age
,则返回值类型为number
。
2.4.type关键字
除了直接使用类型关键字来限制变量的类型之外,我们还可以使用type
关键字来定义类型,例如:
type a = number;
type b = string;
type c = boolean;
type abc = number | string | boolean;
......
使用起来也很简单,将类型关键字直接替换为type
定义的变量即可:
let num:a = 1;
let str:b = 'abc';
// 复杂类型的type
let multi:abc = 2;
multi = 'bcd';
// 限制传入的是有length属性的参数
type strType = string | number[];
const getLength = (obj: strType) => obj.length;
console.log(getLength('daadsfadf'))
console.log(getLength([1,2,3,4,5]))
......
除此之外,type
定义的变量,还可以被赋值为某些具体的值,表示在后续给变量赋值的时候,只能在这些值之间进行选择,例如:
type numType10 = 1 | 3 | 5 | 7 | 9;
let num:numType10 = 1;
// 此处会有错误提示
let num:numType10 = 2;
// 更多的例子
type checkedStateType = 'checked' | 'unchecked';
type lockStateType = 'locked' | 'unlocked';
type visibleStateType = true | false;
3. 总结
- TS的作用是在编译期将可能出现的问题尽可能的暴露出来,从而减少运行期出现bug的概率。
- 给变量定义的类型可以是基础数据类型,也可以是自定义的
interface
,还可以是某些具体的值。 - 可以通过
|
联合的方式将单一的类型限制组合为复杂的多个类型限制。 - 泛型的出现使用函数的封装更加灵活且复用性更高,此外也可以明确的对应出入参的关系。
- 使用
typeof
可以获取到变量的类型,使用keyof
可以获取到对象的字段名类型。