前言
🎈本期将带来的是,typescript的数据类型,大家加油啊,本篇文章会尽量做到让大部分人都能够接受!笔者的能力精力有限,有错误的地方还请大家多多指点包涵!那正片开始!
注意
1. 本期还是一个前提:大家需要明白typescript到底是什么(可参考【TypeScript学习笔记】一、什么是TypeScript及环境配置)并且具备一定的JavaScript基础,对js的语法要有一定程度的了解,若你对本文的js语法还是有些模糊的话,建议你在阅读本篇文章的过程中,对照JavaScript | MDN ,顺便复习一下!
2. 如果你满足难以理解本篇文章中的概念,可先看示例,再去理解概念!
1. 基元类型
我们知道,JavaScript有三种非常常用的类型:string、number、boolean
这三个类型,在TypeScript中都有对应的类型,并且,这些类型名称,与JavaScript运用typeof返回的类型名称相同,这里对这三个类型在js层面进行简单解析:
- string 表示字符串值,如"Hello!ice_dk!"
- number 表示数字值,如42,52.3(注意:js是没有像其他有些语言一样,拥有明显的int、float区别的,一切都只是number类型)
- boolean 表示布尔值,有两个值,分别为true、false
然而,在typescript中,当你使用const、var、let来初始化变量时,可以直接加上类型注释,利用:type就可以显式地指定变量的类型为type,如以下例子:
var str:string ="Hello!ice_cdk!"
const num:number=42
let boo:boolean=true;
诶,看了一下代码,相比之前js,多了一步操作,就是指定好变量的类型,以便于我们后续对类型的判别。 但是,这在大多数情况下,这并不是必需的!因为TypeScript会尝试自动推断代码中的类型。例如:变量的类型是根据其初始化的类型推断出来的:
2. 数组(Array)
明白了基本类型,我们也就开始下一个boss:数组!也是非常容易理解的ts操作!
指定数组的类型可以使用ItemType[ ]或者Array<ItemType>,ItemType指数组元素的类型:
注:Array<ItemType>声明类型的方式使用了TypeScript的泛型语法,后期会出篇博客进行介绍
const arr1:number[]=[1,2,3]
const arr2:string[]=['1','2','3']
跟上面的基本类型一样,对已经指定好类型的数组,我们同样是不能够去添加其他类型的数据的,否则typescript会抛出错误
3. 元组
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为string
和number
类型的元组。
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
4. 函数
TypeScirpt允许你指定 -> 函数输入值和输出值的类型
参数类型
能够指定输入参数的值
// 参数类型定义
function getName(name: string) {
console.log("Hello, " + name);
}
此时,当你调用getName函数的时候,若是传入的参数不符合预期,ts会抛出错误
值得注意的是:当你没有对参数的类别进行注释的时候,ts仍会检查您是否传递了正确数量的参数!
返回值类型
同样,我们也能够注释返回值的类型
function getName(name: string): string {
console.log("Hello, " + name);
return "Hello, " + name;
}
此时,若你对这个指定返回值为string类型的函数,返回一个其他类型的,ts就会抛出错误
注意:该类型与变量类型注释非常类似!通常不需要进行注释,因为TypeScript会根据return语句推断函数的返回类型!(有些使用者可能出于严谨性的缘故,可能还是会坚持进行注释,具体是否使用:看个人喜好及团队要求)
匿名函数
匿名函数与普通函数声明有点不同,当一个函数出现在TypeScript 可以确定 它将被调用的地方时,该函数的参数会自动指定类型!
我们可见,就算参数s没有类型注释,typescript也会使用forEach函数的类型,以及数组的推断类型来确定参数s的类型,这个过程称为上下文类型(即:函数发生在程序中,其上下文会通知它应该是什么类型)
与推理规则类型,你不需要明确了解这是如何发生的,但了解了它的机制,可以帮助你注意何时不需要类型注释。
5. 对象
除基元类型以外,你会遇到最常见的类型:对象类型。
这指的是任何带有属性的 JavaScript 值,几乎是所有属性!要定义对象类型,我们只需列出其属性及类型。
let obj: { x: number; y: number } = { x: 1, y: 2 };
此时,对于指定类型的对象,若是其值不符合指定的类型时抛出错误:
可选属性
在指定的类型属性名后加上一个 ?,可以指定该属性为可选属性
let obj: { x?: number; y: number } = {
y: 2, // x 是可选属性,对象内不含x属性时将不再抛出错误
};
此时,当你想去使用这个obj的时候,你需要考虑到x是否会被传入的情况,以免导致不必要的错误
而正确的做法是:
function f(obj: { x?: number; y: number }) {
console.log(obj.y++);
if (obj.x) { // 判断可选属性是否存在
console.log(obj.x++);
}
}
6. any
any是typescript的一个特殊类型,当你不希望某个特定值导致类型检查错误时,就可以使用它。
当一个变量类型为any,你便可以访问它的任何属性,将它分配为任何类型的值,或者几乎任何语法上的东西都是合法的
let obj: any = { x: 0 };
// 以下代码行都不会抛出编译器错误。
// 使用'any'将禁用所有进一步的类型检查
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
诶诶诶,你什么意思?你这不砸我坑嘛,凭啥,我用ts不就是有个目的是为了做类型检查嘛,按道理来说,我更希望使用类型检查才对啊,这不是违背了typescript的目的嘛?
这似乎确实是这样,不是什么好东西,但试想,我们有时可能需要描述一个我们根本不知道类型的变量,比如一些未知的第三方库的变量参数等;或者,有时你不想指定好类型,只是为了确保代码能够运行,或许你便可以使用any来检测
但请注意:万万不可大量使用any类型,因为any没有进行类型检查,使用它就相当于在使用原生JS,失去了TS的意义!!!
7. unknown
与 any 类型相似,可以设置任何的类型值,随后可以更改类型,但 unknown 要比 any 更加安全,看个例子:
let a: any = "ice_cdk";
a = [];
a.push("0");
上面代码在编译与运行时都是正常的,但是当我们手误写错了 push 方法,你就会发现问题所在:
这段代码编译时不会报错,只会在运行时报错,这就失去了 TypeScript 在编译时检查错误的功能,在项目比较大时,参与的人多时,就很难避免这样类似的问题,因此 unknown 类型出现了
虽然我们将其类型更改为数组类型,但编译器认为其依旧是 unknown 类型,该类型没有 push 方法,就会报错,除非我们先判断类型
let a: unknown = "ice_cdk";
a = [];
if (a instanceof Array) {
a.push("0");
}
这样代码就没问题了,当你的 push 方法写错了,编译器就会报错提示你!
虽然有些麻烦,但相比起 any 类型来说,更加安全一点,在代码编译期间,就能帮我们发现由于类型造成的问题,因此在大多的场景中,我们还是会使用 unknown 类型来代替 any
8. 其他类型
void
void 表示不返回值的函数的返回值
function A() {}
const a = A(); // type A = void
只要函数没有任何的 return 语句,或者没有从这些返回语句中返回任何显式值,它的推断类型就是 void
区别:在js中,一个不返回任何值的函数将隐含地返回 undefined 的值,但是在 typescirpt 中,void 和 undefined 是不一样的
object
特殊类型 object 指的任何不是基元的值(string、number、bigint、boolean、symbol、null或undefined)(即对象)
这与空对象类型 { } 不同,也与全局类型 Object 不同,Object 一般用不上,使用的都是object
let a: object; // a只能接受一个对象
a = {};
a = {
name: "Ailjx",
};
a = function () {};
a = 1; // err:不能将类型“number”分配给类型“object”
注意:在js中,函数值是对象,它们有属性,在它们的原型链中有 Objcet.prototype,是 Object 的实例,你可以对它们调用 Object.key 等等,由于这个原因,函数类型在 TypeScirpt 中被认为是 object !
never
never 类型表示的是那些 永不存在 的值的类型
- 可以表示总是抛出异常或根本不会有返回值的函数的返回值类型
function error(msg: string): never {
throw new Error(msg);
}
// 推断出fail返回值类型为never
function fail() {
return error("ice_cdk");
}
// A函数会造成死循环,根本不会有返回值,可以用never来表示返回值类型
function A(): never {
while (true) {}
}
- 被永不为真的类型保护所约束下的变量类型
function Sw(a: boolean) {
switch (a) {
case true:
return a;
case false:
return a;
default:
// 这个分支永远不可能到达
// 此时 _a类型为 never
const _a = a;
return _a;
}
}
- never 类型可以分配给每个类型,但是,没有任何类型可以分配给 never (除了 never 本身)
never 类型在实际开发中几乎使用不到,最大的用处就是表达一个总是抛出异常的函数的返回值类型了
Function
全局类型 Function 描述了 js中所有函数值上属性,如 bind、call、apply和其他属性,即 Function 类型的值可以被任何函数赋值,并且总是可以被调用(不会受参数的限制),这些调用返回的都是 any 类型
let fn: Function;
fn = () => {};
fn = function () {};
fn = function (a: number): number {
return a;
};
// 虽然fn的值是一个必须接受一个number类型参数的函数
// 但因为fn类型为Function,调用fn时可以不传参数
fn();
fn('1',2,true) // 还可以随便传参
const a = fn(1); // a的类型依旧为any
从上面调用 fn 的例子可以找到其并不安全,一般最好避免,建议使用函数类型!
9. 联合类型
定义联合类型
联合类型是由两个或多个其他类型组成的类型,表示可能是这些类型中任何一种的值。我们将这些类型中的每一种称为联合类型的成员。
多个类型之间使用 | 分割:
function getId(id: string | number) {
console.log("id=", id);
}
getId(1);
getId("1");
这个例子中,若是传入的类型不匹配,则会报出错误
使用联合类型
在使用联合类型时,需要注意的是,不能盲目将联合类型的数据当成单独类型的数据进行操作,不然 typescript 将抛出错误提醒你
这里直接对联合类型的 id 进行字符串上的 toUpperCase 操作,typescript 会自动检测 id 联合类型的成员是否都具有 toUpperCase 属性,这里检测到联合类型的成员 number 类型并不具有 toUpperCase 属性,因此会抛出错误提示
正确的做法:
function getId(id: string | number) {
if (typeof id === "string") {
// 在此分支中,TS自动检测id的类型为“string”
console.log(id.toUpperCase());
} else {
// 此处,TS自动检测id的类型为“number”
console.log(id.toString());
}
}
先用判断语句确定 id 具体的类型,再对其进行操作(这个过程叫类型缩小)
10. 类型别名
前面我们声明类型都是直接在类型注释中编写类型来使用它们,这很方便,但是想要多次使用同一个类型,可以用一个名称来引用它!
声明简单的类型
这就可以用类型别名 type 来声明类型
type Id = number | string;
// 在类型注释中直接使用类型别名
function getId(id: Id) {
console.log("id=", id);
}
getId(1);
getId("1");
定义类型别名以及后面所讲的接口时,建议将首字母大写
声明复杂的类型
type Point = {
x: number;
y: number;
};
function printCoord(pt: Point) {
console.log("坐标x的值是: " + pt.x);
console.log("坐标y的值是: " + pt.y);
}
printCoord({ x: 100, y: 100 });
扩展属性(交叉类型)
类型别名也可以用交叉点 & 来扩展类型
type User = {
name: string;
};
type Admin = User & {
isAdmin: boolean;
};
const admin: Admin = {
name: "ice_cdk",
isAdmin: true,
};
这里的 Admin 在 User 基础上扩展了 isAdmin 类型,当使用 Admin 并赋予的类型不匹配时将抛出错误:
这里的报错可以与下面的接口做比较。
说实话,梳理到这里有点小累了,听听歌,再坚持坚持一下吧!!!!
11. 接口
一个接口声明 interface 是另一种方式来命名对象类型
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("坐标x的值是: " + pt.x);
console.log("坐标y的值是: " + pt.y);
}
printCoord({ x: 100, y: 100 });
类型别名与接口的差异
类型别名和接口非常相似,在很多情况下你可以自由选择它们。几乎所有的功能都在 interface 中可用 type ,关键区别在于扩展新类型的方式不同。
接口的扩展是使用 extends 继承(与 class 类的继承相似)
interface User {
name: string;
}
interface Admin extends User {
isAdmin: boolean;
}
const admin: Admin = {
name: "Ailjx",
isAdmin: true,
};
继承后的 Admin 接口包含父类 User 中的所有类型,当不匹配时将抛出错误
这里对比,类型注意中抛出的错误会发现这么一个细节:
从抛出的错误中,我们可以看到,接口抛出的错误时“但类型 "Admin" 中需要该属性。”,而类型别名抛出:“但类型 "User" 中需要该属性”。
可见,类型别名的扩展是将父类 User 与类型{ isAdmin: boolean;}一并交给 Admin 引用,当使用Admin时实际是同时使用了 User 和{ isAdmin: boolean;}两种类型。
而接口的扩展是直接继承父类 User。在父类基础上添加了{ isAdmin: boolean;},并生成一个新类型Admin,使用它,则与 User无关了。
添加新字段
接口也可以向现有的接口添加字段,同名则会被合并,这是类型别名做不到的
interface MyWindow {
title: string;
}
interface MyWindow {
count: number;
}
const w: MyWindow = {
title: "hello ts",
count: 100,
};
建议优先使用接口,接口满足不了是,再用类型别名
12. 类型断言
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“哥们,相信我,我知道自己在干什么!”。
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,靓仔,你已经进行了必须的检查。
类型断言有以下两种写法:
使用 as 指定
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
使用尖括号指定
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
typescript 只允许类型断言转换为更具体或更不具体的类型版本。(能防止“不可能”的强制)
13. 文字类型
除了一般类型 string 和 number,我们可以在类型位置引用特定的字符串和数字,来限定变量只能为特定的值
let MyName: "ice_cdk";
就其本身而言,文字类型并不是很有价值,拥有一个只能有一个值的变量并没有多大用处!
但是通过将文字组合成联合,你可以表达一个更有用的概念。例如:只接受一组特定已知值的函数:
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
参数只能被赋予 left、right、center,这在组件库中非常常见!
数字文字类型,布尔文字类型同样如上,同时你也能够与非文字类型一起公用来实现功能
interface Options {
width: string;
}
function configure(x: Options | 1) {
// ...
}
configure({ width: "haha" });
configure(1);
configure("automatic"); // 报错
文字推理
看这样一段代码
function handleRequest(url: string, method: "GET" | "POST" | "GUESS") {
// ...
}
const req = {
url: "https://blog.csdn.net/m0_51969330?type=blog",
method: "GET",
};
handleRequest(req.url, req.method);
看上去是没啥子毛病的,但其实 ts 是会抛出错误的
在上面的例子 req.method 中推断 string,不是"GET"。因为代码可以在创建 req 和调用之间进行评估,ts 认为这段代码是有错误的。
有两种方法可以解决
1. 通过任一位置添加类型断言来更改推理
方案一:
const req = {
url: "https://blog.csdn.net/m0_51969330?type=blog",
method: "GET" as "GET",
};
>
表示:“我确定 req.method 始终拥有文字类型 “GET“ ””
方案二:
handleRequest(req.url, req.method as "GET");
表示:“我确定 req.method 始终拥有文字类型 "GET" ”
2. 可以使用 as const 将类型转换为类型文字
将整个对象转换成类型文字
const req = {
url: "https://blog.csdn.net/m0_51969330?type=blog",
method: "GET",
} as const;
只将 method 转换成类型文字
const req = {
url: "https://blog.csdn.net/m0_51969330?type=blog",
method: "GET",
} as const;
该 as const 后缀就像 const 定义,确保所有属性分配的文本类型,而不是一个更一般的 string 或 number。
14. null和undefined
JavaScript 有两个原始值用于表示不存在或未初始化的值:null 和 undefined
typescript 有两个对应的同名类型。这些类型的行为取决于你是否设置tsconfig.json / strictNullChecks 选择。
strictNullChecks 表示在进行类型检查时,是否考虑 “null” 和 “undefined”
- strictNullChecks=false 时,下述代码不会错:
function doSomething(x: string | null) {
console.log("Hello, " + x.toUpperCase());
}
- strictNullChecks=true 时(strict =true 时所有的严格类型检查选项都默认为true),上述代码会报错
正确的做法:
function doSomething(x: string | null) {
if (x === null) {
//.....
} else {
console.log("Hello, " + x.toUpperCase());
}
}
- 非空断言运算符(!后缀)
!在任何表达式之后写入实际上是一种类型断言,即确定该值不是 null 或 undefined
function liveDangerously(x?: number | null) {
// console.log(x.toFixed()); // 报错:对象可能为 "null" 或“未定义”。
console.log(x!.toFixed()); // 正确
}
15. 枚举
枚举是 TypeScript 添加到 JavaScript 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。
与大多数 typescript 功能不同,这不是 JavaScript的类型级别的添加,而是添加到语言和运行时的内容。因此,你确定你确实需要先枚举再做些事情,否则不要使用,具体请看TypeScript: Handbook - Enums
TS枚举
enum Direction {
Up = 1,
Down,
Left,
Right,
}
console.log(Direction.Up) // 1
编译后的JS代码
"use strict";
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
console.log(Direction.Up); // 1
16. 不太常见的原语
bigint
从 ES2020开始,JavaScript中有一个用于非常大的整数原语 BigInt
// 通过bigint函数创建bigint
const oneHundred: bigint = BigInt(100);
// 通过文本语法创建BigInt
const anotherHundred: bigint = 100n;
symbol
JavaScript 中有一个原语 Symbol(),用于通过函数创建全局唯一引用
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// 这里的代码不可能执行
}
typescript的数据类型,到此也就讲完啦,感谢你能够坚持看下来,很棒!说实话,我其实在学和写的时候,多少也会感到枯燥,但是只有更清楚,更深刻地理解这些,我们才能变得更强,不是嘛!笔者的能力精力有限,难以最优化地展示作品给大家,有什么不对的地方还望大家多多教导!创作不易,还望大家多多支持!!!
🎈试炼的终点终将会花开万里🌸