前言:
在上一节Typescript的类型声明中,认识到类型声明可针对于三个地方进行类型限制,即:变量、函数参数、函数返回值、
而这一节学习内容是Typescript在Javascript的基础上新增了什么基本类型?保留了什么类型?什么时候应该使用这些类型?应该怎么使用这些类型
希望诸位能抱着以上四个问题去学习Typescript的基本类型
基本类型表:
类型 | 例子 | 描述 |
---|---|---|
number | 1,-11,0.01 | 任意数字 |
string | ”hello“,‘hi’,hi | 任意字符串 |
boolean | true,false | 布尔值(true或false) |
字面量 | 本身 | 限制变量的值就是该字面量的值 |
void | 空值(undefined) | 没有值(undefined和null) |
object | {name:‘张三’} | 任意JS对象 |
array | [1,2,3] | 任意JS数组 |
tuple | [4,5] | 元素,TS新增类型,固定长度数组 |
enum | enum(A,B) | 枚举,TS新增类型 |
any | * | 任意类型 |
unknown | * | 类型安全的any |
never | 不存在的值 | 不能是任何值 |
三个特殊类型:
顶层类型:
1.any类型
any类型是什么?
答:any表示的是任意类型,即可以代表任意一个类型。
为什么将它分类为顶层类型?
答:之所以将any称为顶层类型,是因为他包含了Javascript中的所有类型的值,
可以理解any为所有类型的总称,是一个包含了所有类型的超集
例:
let a: any; // 将变量的类型设置为any类型后,可以赋任意类型的值
a = 123;
a = "hello";
a = true;
需注意,将变量类型设置为any类型后,就可以赋任意类型的值,也可以被赋值给任意类型的其他变量
应该在什么时候使用any类型进行类型限制?
答:使用TS时,不建议使用any类型。
既然不建议使用。那它存在的意义是什么?
答:这就要从Typescript的类型安全检测说起,在上一节曾经强调过,之所以引入类型,是因为我们需要TS帮助我们去检测声明的类型,或者说使用TS有类型安全这一好处。
使用了any类型,Typescript编译器就不会清楚哪些操作是被允许,哪些操作是被禁止的,就失去了Typescript提供的类型安全方面的好处,就与直接用JavaScript没有什么区别,失去了使用Typescript的意义
let a: any = true;
let c: number;
c = a; // 在将变量a设置为any类型后,可以将a变量赋值给任意类型的其他变量
// 上方变量c为number类型,但是在将any类型的变量a赋值给变量c时,并不会报错
console.log(c) // true
如上代码,我们明明声明变量c为number类型,但是由于有一个变量a为any类型,赋值之后导致number类型的变量c输出为true,这就是使用any类型的缺点。
所以不应该在生产环境中去使用any类型,在生产环境中应该将any类型全部替换
但是,如果你希望尽快实现一个可行性的方案,又不想让Typescript对你约束太大,就可以在代码编码及调试阶段使用any类型 ,尽快实现你的方案
2.unknow类型
unknow类型是什么?
答:unknow类型,表示未知类型的值,与any类型相似,也是一个顶层类型,是所有类型的总和,亦是一个超集,所以unknow类型也可以赋任意类型的值
既然它与any类似,为什么要引入?
答:上文提到,在生产环境中使用any类型,会导致一些安全隐患,所以我们需要在生产环境中尽量的替换any类型,但是有的时候,有些变量或函数的返回值与参数我们确实不能明确类型,如第三方输入以及接口返回数据等。那这个时候,我们就可以将类型声明为unknown类型,接收所有类型的值。
例:
let a: unknown; // 声明为unknown类型后,就可以将任意类型赋值给该变量
a = 123;
a = "hello";
a = true;
注意:我们可以将unknow理解为安全的any类型。
为什么它是安全类型的any?
作为any类型的替代,它一定是弥补了any类型的缺点,,即unknown可以保证类型安全。unknown为了保确保使用unknown的类型安全,unknown不允许使用调用任何方法,任何方法的调用都会引发编译错误,
例:
let value: unknown;
let boolValue: boolean = true
value = boolValue
value.charCodeAt(0) // 报错
只有在针对unknown进行类型安全检测之后,才可以允许执行特定的操作。
例:
let value: unknown;
let boolValue: boolean = true
value = boolValue
if (typeof value === "string") {
value.charCodeAt(0) // 不报错
}
为什么要进行这样的设计?
答:我们可以这么理解,类型声明,是作用于一个”数据容器“上的,这个容器有一个输入口,一个输出口,在这个容器中进行一系列数据操作。
有一天,为了安全,我们限制了输入口的”数据流“,例如,限制为只有”string“类型才能进入容器内部,但这种设计有一个局限,假如我们确实需要让一些不明确类型的数据流进入容器内部进行操作,但是由于类型限制,数据流不能正确规定进入的入口,我们针对这种情况,给这种不明类型的数据流开了一个”后门“。
规定”任何不明类型的数据流都可以经过这个后门进入容器内部,但不能在"监护人"不在的情况下进行任何操作!只有当"监护人"进行安全检测后才可以继续操作“这其中的监护人,就是我们编码者,这种设计强迫着编码者在需要操作不明类型的数据时,必须进行一系列的安全判断。
也就是说,TS可以允许任意类型存在,但是在使用这种特殊的任意类型时,必须由使用者进行类型安全判断,既然你想享受到TS的类型安全”好处“,那么在TS保证不了安全的情况下,将保证类型安全这个担子放在了编码者身上,TS只是加了一条规则强迫编码在使用unknown时必须执行安全责任,这种设计不能不说是特别聪明的。
应该在什么时候使用unknown类型进行类型限制?
答:在需要使用any类型,并且又希望编译器提供类型安全的时候,就可以使用unknown
补充: 这种使用前执行类型检查的过程,称作"类型窄化"。
底层类型:
1.never类型
概念:never类型,表示永远不会出现的值的类型,即never表示“无”这种状态。
为什么称它为底层类型?
答:它是所有类型的子类型,可以赋值给任何其他类型,但是任何类型都不
能赋值给never,其实可以将整个基本类型理解为下图,any以及unknown
包含所有其他基本类型。简单来说,never用来描述一种不存在的状态。
这种底层类型,实际上就是一个空集,不包含任何类型的值的类型。
这种空集类型有什么作用呢?never应该什么时候使用?
答:我们可以利用never来表示一种情况:永远没有返回值的函数,见下例
例:声明返回值为never的函数,就必须存在无法到达的终点,即这个函数永远不能执行结束
1.函数中途必定中断:
function error(message:string):never{
console.log('函数开始!!');
throw new Error(message) // 函数运行到这里,必定会抛出一个错误,抛出错位意味着这个函数被中断
console.log('函数结束!!'); // 由于上文的异常抛出,函数永远不会执行此条输出
}
error(); // 执行函数error
2.函数永不结束
function alwaysCycle():never {
while(true){
console.log(1); // 函数进入一个死循环,就意味着这个函数永远不会运行结束
}
}
never的具体使用---never实现穷举类型检查
现有一个函数,函数paramsType函数实现的功能是根据传入参数输出参数类型
function paramsType(valueType: number | boolean) {
if (typeof valueType === "number") {
console.log('输入参数类型为Number');
} else {
console.log('输入参数类型为Boolean');
}
}
parparamsType(true); // "输入参数类型为Boolean"
突然有一天,你的同事需要拓展函数参数的参数的联合类型。
似上方paramsType函数的参数类型声明方式即为联合类型,"|" 相当于 "或" ,即参数valueType既可以是number类型又可以是boolean类型
拓展后的联合类型为"number | boolean | string" ,但是由于疏忽,并未添加相应的判断分支来处理新增的参数类型,如下
function paramsType2(valueType: number | boolean | string) {
if (typeof valueType === "number") {
// TODO operate on value as number
console.log('输入参数类型为Number');
} else {
// TODO operate on value as boolean
console.log('输入参数类型为Boolean');
}
}
paramsType2('hello'); // 输入参数类型为Boolean
此时调用函数后,既没有编译器警告,也没有运行时的错误输出,只能根据代码逻辑判断这个缺陷
为了解决这没有警告的问题,我们可以使用never类型,即在初次创建这个函数时,就创建一个判断分支,用于接收输入值,并将其赋值给一个类型为never的常量,将paramsType优化为下函数function paramsType3:
function paramsType3(valueType: number | boolean) {
if (typeof valueType === "number") {
console.log('输入参数类型为Number!');
}else if(typeof valueType === "boolean"){
console.log('输入参数类型为boolean!');
}
else {
const neverArriveHear:never = valueType;
//若拓展了参数类型,但是没有做出相关的逻辑处理,此处必定出错
}
}
paramsType3(true); // 输入参数类型为boolean
以上代码,如果逻辑正确,那么代码就可以编译通过,但是有一天,你或你的同事拓展了函数参数的类型,但是他忘记了在函数体内容中对拓展进行逻辑处理
那么只要拓展,在最后的never分支valueType就会收窄到添加的类型,导致无法赋值给never类型的常量,产生一个编译报错,通过此种方法,你就可以保证该函数穷尽了所有类型
亦可以理解为利用了never拥有无法到达的终点这一特质,只要到达了never分支,那么你的逻辑必定是出错的。起到一个兜底的作用
function paramsType3(valueType: number | boolean | string) {
if (typeof valueType === "number") {
console.log('输入参数类型为Number!');
}else if(typeof valueType === "boolean"){
console.log('输入参数类型为boolean!');
}
else {
const neverArriveHear:never = valueType;
//上方拓展了参数类型新增了一个string,但是没有做出相关的逻辑处理,此处出错
}
}
报错提示,就保证了我们如果出现逻辑错误,编译器提示错误,而不会出现如上编译正常,浏览器亦不报错的情况,帮助编码者发现逻辑错误:
对于any类型以及unknown类型的补充(1)----类型断言
一般情况下,不进行类型窄化是不能运用任何方法的:
let a: unknown;
a = "hello";
console.log(a.length); // 此时由于变量a为unknown类型,所以我们在明知变量a的内容为“string”类型的情况下,却并不能使用字符串的length方法
而除了类型窄化,当变量类型为unknown类型时,如果代码编写者明确的知道该变量内容的类型,就可以使用类型断言进行解释性的强制转换,进而使用断言之后的类型方法,见例:
1.变量名 as 类型名
let a: unknown = "hello";
console.log((a as string).length); // 5
类型断言的第二种方法,注意这种断言方法有兼容性问题,在使用到JSX时,只有as语法的断言时被允许的
2.<类型名>变量名
console.log((<string>a).length); // 5
须注意,类型断言只是一个断言,不是将变量a的类型真实的转化为了某类型,它只是此刻告知编译器变量a为某种类型,就像指鹿为马一样,你说它是马编译器就认为它是马,但是却不能在客观上改变他是鹿的事实,见下例
let a:unknown;
function changeType(){
console.log('变量a开始类型断言');
a as string; // 此刻只是进行了断言,而没有其他变量接收断言后的数据类型
console.log('变量a结束了类型断言');
}
changeType();
let b: string = a // 报错,因为类型断言只是这一刻通知,并没有实现将a的类型进行转换
报错: