Appearance
基础类型
类型含义
js
javascript 本身提供类型支持,但有以下几个问题。
- js 是弱类型,类型可以根据值发生改变
let a = 1
let b = 2
let c = a + b;
//改变值后,a 的类型是字符串,这是不稳定的
a = '斑马兽';
c = a + b;
- 使用 js 不能自定义类型,比如下面的 name 我们想只允许是 banmashou、bmcms 值,但实际上是可以传递任何值的。
function getTitle(name) {
return name === 'banmashou' ? '斑马兽' : 'bmcms 系统';
}
getTitle('斑马') //不报错
如果换成 ts 就方便了,我们可以定义类型来修饰参数 name,使其值只能为 banmashou 或 bmcms
function getTitle(name: 'banmashou' | 'bmcms') {
return name === 'banmashou' ? '斑马兽' : 'bmcms 系统';
}
getTitle('斑马') //报错
typescript
可以将 typescript 中的类型理解为一类值的集合,比如 'banmashou'、'斑马兽'都属于 string 类型集合。
有些类型没有值比如 never,不能将任何值赋予 never 类型的变量
有些类型只有一个值,下面的
bm
变量的值只能是字符串a
type BANMASHOU='a' ;
const bm:BANMASHOU='a'
- string、number 是无限集合,而上例中的 BANMASHOU 类型为有限集合
类型校验
下面没有使用类型限制时,函数参数传入字符串也是可以执行的,显示这个结果是不对的
function sum(a,b){
return a+b;
}
console.log(sum('a',3)); //结果为 a3
加上严格类型后,在编译环节就会提示错误
function sum(a:number,b:number){
return a+b;
}
console.log(sum('a',3))
//报错 Argument of type 'string' is not assignable to parameter of type 'number'.
类型推断
当没有明确设置类型时,系统会根据值推断变量的类型
字符串
下例中系统会根据值推断 bm 变量为 string,当将 bm 设置为 18 的 number 类型时编译时将报误
let bm = 'banmashou.com'; //let bm: string
bm = 18;
数值
ts 中的数值类型包括了小数、负数、整数
let bm =100 //let bm: number
bm = 100.1
bm = -101
布尔值
值为 true 或 false 会被推断为 boolean 类型
let state = true; //let state: boolean
数组
下面是数组类型的推断结果,表示数组内容值为字符串
const bm = ['banmashou.com', '斑马兽'] //const bm: string[]
bm.push(100) //因为类型不允许,所以报错
下面会推断数组允许的值为字符串或数值
const bm = ['banmashou.com', '斑马兽',100] //const bm:(string|number)[]
bm.push(100,'斑马') //数组允许数值、字符串类型,所以编译通过
对象
ts 也可以推断字面量类型
const user = {name:'斑马兽',age:18,open:true}
推断结果如下
const user: {
name: string;
age: number;
open: boolean;
}
如果向对象中添加类型中不存在的属性将报错
const user = {name:'斑马兽',age:18,open:true}
user.city = '北京'
//将产生错误 Property 'city' does not exist on type
下面是更复杂的在对象中嵌套对象,TS 也是可以推断出来的
const user = {name:'斑马兽',age:18,open:true,lessons:[
{title:'linux'},
{title:'TS'}
]}
user.lessons[0].title
上例推断的结果是
const user: {
name: string;
age: number;
open: boolean;
lessons: {
title: string;
}[];
}
配置文件
TS 支持对编译过程使用配置项自定义,因为下面要讲的有些类型在不同 TS 配置时有差异,所以我们要掌握 TS 配置文件的创建与使用。
初始化
配置项的具体选项使用,会在讲到某个知识点用到时再具体说
执行以下命令创建配置项
tsc --init
然后执行以下命令使用配置项的定义进行监测
tsc -w
也可以使用 vscode 终端 > 运行任务 >typescript
菜单运行监视
配置选项
配置 | 说明 |
---|---|
noImplicitAny | 禁止使用隐含的 any 类型,如函数参数没有设置具体类型 |
strictNullChecks | 开启时不否允许将 null、undefined 赋值给其他类型比如字符串 |
target | 转换成 JS 的版本 |
strict | 是否严格模式执行 |
module | 使用的模块系统 |
基本类型
除了上面的类型自动推断外,更多的时候是明确设置变量类型
字符串
字符串使用 string 来声明
const bm:string = 'banmashou.com'
数值
在 TS 中不区分整数与浮点数,都使用 number 来声明
const bm:number = 100
布尔
使用 boolean 来声明布尔类型
const bm:boolean = true
数组
下面是对数组值类型为字符串
let bm:string[] =[]
bm.push('banmashou','斑马兽')
也可以使用泛型来声明数组(泛型的详细使用后面内容会介绍)
let bm:Array<string> =[]
bm.push('banmashou','斑马兽')
创建值类型字符串的数组,并填充内容为banmashou.com
let bm = new Array<string>(3).fill('banmashou.com')
console.log(bm);
元组
明确数组每个成员值类型的数组为元组
let bm: [string, number, boolean]
bm = ['banmashou.com', 2090, true]
console.log(bm);
对象
下面是声明对象类型但不限制值类型
let bm:object
bm ={name: '斑马兽'}
bm = {} //使用字面量声明对象
bm = [] //数组是对象
bm = Object.prototype //原型对象
bm='banmashou' //报错,改变了类型为字符串
限定对象值类型
let bm:{name: string,year:number}
bm={name:'斑马兽',year:2010}
属性后面跟上?
用来指定 url 为可选值,这样的属性是非必填项
let bm:{name: string,year:number,url?:string}
bm={name:'斑马兽',year:2010}
索引签名
如果有明确的索引名称可以使用下面方式来定义签名
type BANMASHOU = {
name: string
city: string
}
let bm: BANMASHOU = {
name: 'banmashou',
city: 'beijing'
}
如果定义任意属性的签名,可以使用索引签名完成
type BANMASHOU = {
[key: string]: keyof any
}
let bm: BANMASHOU = {
name: 'banmashou'
}
也可以添加明确的索引
type BANMASHOU = {
[key: string]: keyof any
city: string
}
let bm: BANMASHOU = {
name: 'banmashou',
city: 'beijing'
}
下例中我们要求索引后有 Bm 的后缀,则可以使用模板字面量形式
type BANMASHOU = {
[key: `${string}Bm`]: keyof any
}
let bm: BANMASHOU = {
nameBm: 'banmashou'
}
当然也可以使用 Record 工具类型来定义
type BANMASHOU = Record<string, string>
let bm: BANMASHOU = {
name: 'banmashou'
}
Record 可以使用联合类型定义索引
type BANMASHOU = Record<'name' | 'age' | 'city', string>
let bm: BANMASHOU = {
name: 'banmashou',
age: '18',
city: 'beijing'
}
any
使用 any 指包含所有值的顶部类型,所以 any 不进行类型检查,等于关闭了 TS 对该变量的严格类型校验
- 使用 any 类型等同于使用纯 JavaScript 的开发方式
- any 类型是顶部类型,所有其他类型是他的子类型
- 使用 any 类型将失去 typescript 静态类型的强制检测
- 只有在描述一个根本不知道的类型时使用 any
可以将 any 视为所有类型的组合表示
let bm:string|boolean|number;
bm = '斑马兽'
let banmashou:any
bm = '斑马兽'
下面是设置基本 any 的示例
let bm:any
//以下赋值不会报错
bm='banmashou'
bm=2010
bm=true
bm=[]
bm ={}
bm= class{}
在数组中使用 any 类型,可以设置任意类型的值
let bm:any[] =['banmashou.com','斑马兽',2010,true]
也可以使用泛型的方式设置 any 类型数组
let bm:Array<any> =['banmashou.com','斑马兽',2010,true]
为对象属性设置类型
let bm:{
name:any,
year:any
}
//以下设置都不会报错
bm={name:'斑马兽',year:2010}
bm={name:2010,year:'斑马兽'}
any 太过宽泛所以不建议使用,他会丢失 TS 的严格类型校验,比如下面的示例并不会报错
let bm:any
bm.get() //不会报错
下面再来看一下对象的使用 any 类型造成的问题
class Bm {
constructor() { }
get = () => 'banmashou'
}
const obj:any = new Bm;
console.log(obj.get());
obj.show()
所以上例需要指定正确的 Bm 类型,而不是使用 any
...
const obj:Bm = new Bm;
...
能过设置 tsconfig.json 的 noImplicitAny=true
配置项,可以禁止隐含的 any 类型。以下代码会在编译时报错
function sum(a, b) {
return a + b
}
unknown
unknown 类型也是顶部类型这与 any 一样
unknown 用于表示未知的类型
会进行 TS 的类型检查,any 不进行 TS 检查
使用 unknown 类型时可以使用
as
类型断言来明确类型
下面是 any 与 unknown 赋值上的区别,unknown 需要明确类型后赋值,any 则不需要
let bm:any ='斑马兽'
let bmcms:unknown = 'banmashou'
let a:string = bm
let b:string=bmcms //报错: 'unknown'未知类型不能赋值给'string'类型
// unknown 类型需要明确类型后赋值
let c:string=bmcms as string
可以把任何值赋值给 unknown 类型,但在使用时需要指明类型
let bm: unknown
bm = 'banmashou'
bm = 100
//在使用时,TS不知道是什么类型,所以需要使用类型断言进行告之
let c = bm as number + 20
使用 keyof 类型工具时 unknown 与 any 的区别
type BM<T> = { [P in keyof T]: string }
//{[x: string]: string;}
type BANMASHOU = BM<any>
//结果为{},因为 keyof unknow 是never,所以被忽略了
type HJ = BM<unknown>
不同类型赋值时会报错
let bm:string ='99'
let hj:number =bm as number //报错,TS 认为字符串转数值会出现错误
这里需要使用 unknown 做个中间层转换,将其先转换为 unknown 未知类型,再转换为 string 类型
let bm:string ='99'
let hj:number =bm as unknown as number
any 与 unknown 在类型检查上是有区别的
let banmashou: any
banmashou.show();//any不进行类型检查,所以不会报错
let bm: unknown
bm.show();//unknown进行类型检查,unknown是未知类型所以报错
使用 any 类型 ts 不进行类型校验,所以在编译时不会报错,但执行编译后的 js 后会显示 NaN
function get(val: any) {
val = val * 100;
return val
}
console.log(get('斑马兽')); //NaN
使用 unknown 类型时,结合 typeof 进行类型判断,根据不同类型使用不同逻辑
function get(val: unknown) {
if (typeof val === 'number') {
return val * 100;
}
return 0
}
console.log(get(100)); //NaN
null & undefined
null 与 undefined 也是变量类型,用于定义值为 null 或 undefined
let bm:null =null
let banmashou:undefined=undefined
console.log(bm,banmashou);
下面是函数返回值的使用
function getName():string |null{
return null
}
console.log(getName());
strictNullChecks
当配置项启用 strictNullChecks
时,null 与 undefined 只能赋值给 void、null、undefined 类型
let bm:string =undefined; //配置strictNullChecks=true 时将报错
在TS中null与undefined使用与js是有区别的,下面的代码是有问题的,因为null没有toLowerCase()方法。但默认是不报错的,在tsconfig.json配置文件中定义 "strictNullChecks":true
或 "strict": true
将会对代码进行报错提示。
function render(content: string) {
console.log(content.toLowerCase())
}
render(null)
void
void 类型的值为 null 或 undefined,常用于对函数返回值类型定义
- 严格模式(tsconfig.json 配置中关闭
strict
)时,void 值只能是 undefined(有关 TS 配置会在后面章节介绍) - 如果函数没有返回值请使用 void 类型,这会使用代码更易读,并可对不小心造成的函数返回内容进行校验
- 你也可以将 void 理解为对返回 null 或 undefined 的函数返回值声明
- TypeScript 中,不返回任何内容的 void 函数实际上返回的是 undefined
void 类型的值可以是 null 或 undefined,但如果 TS 配置开启了 strict
或 strictNullChecks
则不允许 void 为 null
let bm:void = undefined;
let banmashou:void = null;
void 不允许设置其他类型
let bm:void
bm='banmashou.com' //设置string 将报错
TypeScript 中,不返回任何内容的 void 函数实际上返回的是 undefined
function bm(): void {
}
let hj = hbmd();
hj = undefined
经过 void 限定后是不允许函数返回内容的,所以以下代码将报错
function bm():void{
return 'bm'
}
never
never 是任何类型的子类型,可以赋值给任何类型,没有类型是 never 的子类型。
never 类型的特点
- never 没有任何子类型,所以任何类型都不可以赋值给 never
- 函数抛出异常或无限循环时返回值是 never
- 可以将每个类型理解为某类值的集合,比如 number 类型包含所有数字,但 never 类型没有任何值。
function bm():never{
throw new Error("出错了")
}
never 是所有类型的子类型,可以分配给任何类型,所以下面类型为 string
type BANMASHOU = never extends string ? string : boolean //string
其他类型不可以分配给 never 类型
type BANMASHOU = string extends never ? string : boolean //boolean
never 是所有类型的子类型,所以下面实际类型是 string | number
type BANMASHOU = never | string | number //string | number
union 联合类型
union 联合类型是多个类型的组合,使用 |
进行连接,|
类似于 javascript 中的 ||
或运算符。
下面是为变量声明字符串或数值类型
let bm:string | number = 'banmashou.com'
bm = 2010
下面是为数组声明多种类型
let bm:(string | number | boolean)[] = []
bm.push('banmashou.com',2010,true)
也可以使用泛型方式声明(泛型的详细使用后面内容会介绍)
let bm:Array<string|number|boolean> = []
bm.push('banmashou.com',2010,true)
函数参数是联合类型时,可以使用 typeof 进行判断后分别处理,ts 会根据条件进行类型推断
type BM = {
name: '斑马兽'
}
function get(a: string | BM) {
if (typeof a === 'string') {
a.includes('banmashou')
} else {
return a.name
}
}
交叉类型
交差类型是将 interface、object 等进行合并,组合出新的类型
- interface、object 进行属性合并
- 交叉时要保证类型是一致的,string 与 number 交叉将得到 never 类型
对象类型会进行属性合并
interface A { name: string }
type B = { age: number }
let c: A & B = { name: '斑马兽', age: 100 }
两个类型有相同属性,且类型不同时,返回类型为 never
let a = { name: '斑马兽' }
let b = { age: 10, name: true }
type BM = typeof a & typeof b
//报错 不能将类型“string”分配给类型“never”。
let c: BM = { age: 30, name: 'banmashou' }
上面的问题可以使用 Pick 类型工具移除 name 索引
let a = { name: '斑马兽' }
let b = { age: 10, name: true }
//通过Pick移除name索引
type BM = typeof a & Pick<typeof b, 'age'>
let c: BM = { age: 30, name: 'banmashou' }
通过交叉类型将 User 类型组合成新的 Member 类型
type User = { name: string, age: number }
type Member = { avatar: string } & User
let member: Member = {
name: 'banmashou', avatar: 'hj.png', age: 30
}
下面是属性合并函数的类型定义
function merge<T, U>(a: T & U, b: U): T & U {
for (const key in b) {
// a[key as Extract<keyof U, string>] = b[key] as any
a[key] = b[key] as any
}
return a;
}
string 和 number 因为类型不同,交叉计算后得到 never 类型
type BM = string & number;
type BM2 = 'a' & 'b'
联合类型交叉
type BM = ('a' | 'b') & ('a' ) // a 因为字符串'b'与右侧联合类型交叉后得到never,所以被过滤了
type BM2 = ('a' | 'b') & ('a' | string) // a |b
函数
下面我们来掌握函数在 TypeScript 中的使用方式。
函数定义
下面是 TS 自动推断的函数类型
let bm = ()=>'斑马兽'
bm='banmashou.com' //更改类型为字符串后将报错
我们可以使用 unknown 转为字符串,但这也没有意义
let a:string=bm as unknown as string
下面是使用显示类型定义函数 ,注意类型要使用大写的Function
这与 string/number/boolean 是有区别
let bm:Function
bm = ()=>'banmashou.com'
console.log(bm());
参数类型
下面是没有限定类型的函数定义,代码是不稳定的
function sum(a, b) {
return a + b;
}
console.log(sum('a', 3));//a3
因为这是个计算函数,下面来设置参数类型,让代码更健壮。
- 因为限定了数值类型,所以函数参数必须传递数值
function sum(a: number, b: number) {
return a + b;
}
console.log(sum(2, 3));
如果参数是可选的,使用 ?
修饰
- 下面的ratio 参数可以不传
- 不传时ratio 值为
undefined
function sum(a: number, b: number, ratio?: number) {
return a + b;
}
console.log(sum(3, 3));
如果参数设置默认值了就不需要可选参数符号?
了
function sum(a: number, b: number, ratio: number = .8) {
return (a + b) * ratio;
}
console.log(sum(3, 3));
返回值类型
下面是系统自动推断的参数返回值为 number
function sum(a: number, b: number) {
return a + b;
}
//函数结构为 function sum(a: number, b: number): number
我们也可以明确返回类型
function sum(a: number, b: number): string {
return `计算结果是:${a + b}`;
}
console.log(sum(3, 3));
下面是箭头函数的表示方法
- 因为函数体只有一条语句,所以省略了
{}
let sum = (a: number, b: number): string => `计算结果是:${a + b}`
当函数没有明确返回值时,使用 void 类型。TS 会自动推断,建议明确声明 void 类型
let bm = (): void => {
console.log('斑马兽');
}
bm()
参数声明
有时多个函数会用到相同的类型的参数,比如下面的示例
let addUser = (user: { name: string; age: number }): void => {
console.log('添加用户')
}
let updateUser = (user: { name: string; age: number }): void => {
console.log('更新用户')
}
updateUser({ name: '斑马兽', age: 18 })
我们可以使用 type 对参数对象进行声明,通过这种复用的手段可以很好的优化代码
type userType = { name: string; age: number }
let addUser = (user: userType): void => {
console.log('添加用户')
}
let updateUser = (user: userType): void => {
console.log('更新用户')
}
updateUser({ name: '斑马兽', age: 18 })
函数定义
对没有返回值函数的定义
let bm: () => void
bm = (): void => console.log('斑马兽')
下例是对 bm 函数的定义
- 函数定义中声明的变量 a,在具体实现函数是可以为任何名称
let bm: (a: number, b: number) => number
bm = (x: number, y: number): number => {
return x + y
}
也可以在声明函数时就定义函数的结构
let bm: (a: number, b: number) => number = (x: number, y: number): number => {
return x + y;
}
console.log(bm(2, 3));
参数是对象结构的函数定义
- 下例中的参数
u
不定义类型结构,TS 也是可以推断出来的
let addUser: (user: { name: string, age: number }) => boolean;
addUser = (u: { name: string, age: number }): boolean => {
console.log('添加用户');
return true;
}
上例中使用了重复的参数描述 { name: string, age: number }
,下面我们将参数对象使用 type 进行描述,就可以很好的优化代码
type userType = { name: string, age: number }
let addUser: (user: userType) => boolean;
addUser = (u: userType): boolean => {
console.log('添加用户');
return true;
}
addUser({ name: '斑马兽', age: 12 })
上面是将参数使用 type 进行了描述,我们也可以将函数结构使用 type 进行描述
type userType = { name: string, age: number }
type addUserFunc = (user: userType) => boolean;
let addUser: addUserFunc = (u: userType): boolean => {
console.log('添加用户');
return true;
}
addUser({ name: '斑马兽', age: 12 })
剩余参数
下面的求合函数接收多个参数
function sum(...args: any[]): number {
return args.reduce((s, n) => s + n, 0);
}
console.log(sum(1, 2, 3, 4, 5));
下面通过第二个参数接收剩余参数,来实现数据追加的示例
function push(arr: any[], ...args: any[]): any[] {
arr.push(...args)
return arr;
}
const bm: any[] = ['banmashou.com']
console.log(push(bm, '斑马', '斑马兽')); // [ 'banmashou.com', '斑马', '斑马兽' ]
Tuple 元组
元组与数组类似,但元组要为每个值进行类型声明。
数组只是定义了值的类型,并没有约束某个位置的值必须是什么类型,请看下例
const arr: (number | string | boolean)[] = ['斑马兽', 2030, true];
arr[1] = 'banmashou.com' //不会报错,可以将原来是数值的更改为字符串,这是数组允许的类型范围
arr[10] = '斑马' //不会报错,类型也是允许的
console.log(arr);
下面看使用元组来限制值的类型
const bm: [string, number] = ['斑马兽', 2030]
bm[0] = true //报错,第一个值必须是字符串
函数重载
函数的参数类型或数量不同时,会有不同的返回值,函数重载就是定义这种不同情况的函数。
重载签名
重载签名是对函数多种调用方式的定义,定义不同的函数参数签名与返回值签名,但是没有函数体的实现。
- 使用函数时调用的是重载签名函数,在 vscode 代码跟踪时也会定位到重载签名
- 将从第一个重载签名尝试调用,向下查找是否有匹配的重载签名
- 定义重载签名可以在 idea、vscode 中拥有更好的代码提示
function getId(id: string): string;
function getId(id: number): number;
实现签名
实现签名是是函数功能的实现,对参数与返回值要包扩符合函数签名的宽泛类型。
- 重载签名可以是多个,实现签名只能是一个
- 实现签名是最终执行的函数
- 用户在调用时调用的是重载签名
- 重载签名可被调用,实现签名不能被调用
- 实现签名要使用通用类型
//重载签名
function getId(id: string): string;
function getId(id: number): number;
//实现签名
function getId(id: unknown): unknown {
if (typeof id === 'string') {
return id;
}
return id;
}
//function getId(id: string): string (+1 overload)
getId('斑马兽');
实现签名要使用通用的类型
function getId(id: string): string;
function getId(id: number): number;
//报错:因为实现签名不通用 「不能将类型“unknown”分配给类型“string”」
function getId(id: unknown): string {
if (typeof id === 'string') {
return id;
}
return id;
}
getId('斑马兽');