TS入门:基础类型
2024年7月21日
前言
TS
对于当今的前端世界来说,已经算是普及开了,不仅各个知名项目、框架都在用,就连面试,也算是很多公司的必考题了。所以,学习TS
的重要行就不言而喻了。鉴于此,从今天开始,我们就正式来学习TypeScript
。
首先,我采用的学习方法是一遍看官方文档,一边写Demo
,当然每个人的学习方式都不一样,此文章仅用来记录我个人的学习过程,以加深对相关知识的理解、巩固学习成果,也方便日后可以温故而知新。
废话就不多说了,下面我们正式开始!
字符串
const str: string = 'hello';
布尔值
const isOpen: boolean = true;
const hasDiv: boolean = false;
Number
const num: number = 20;
数组
// 常规写法 - 在元素类型后面接上 []
const arr: string[] = ['hello', 'world'];
// 泛型 - Array<元素类型>
const arr2: Array<number> = [1, 2, 3];
// 多类型元素数组写法
const arr3: (number | boolean | string)[] = [1, true, 'test'];
// 访问元素
const a1 = arr3[0]; // 1, 类型为 number | boolean | string
const a2 = arr3[1]; // true, 类型为 number | boolean | string
const a3 = arr3[2]; // 'test', 类型为 number | boolean | string
// 添加新元素
a.push(42); // 添加 number 类型的元素
a.push(false); // 添加 boolean 类型的元素
a.push("hello"); // 添加 string 类型的元素
元祖 - Tuple
元组表示已知元素类型和数量的数组
const tuple: [string, number]; // = ['hello', 2];
tuple[0] = 'hello'; // => OK
tuple[1] = 1; // OK
// 不能将类型"false"分配给number
tuple[1] = false; // Error
// 不能给超过长度的元素赋值,定义了2个元素,现在设置给第三个赋值
// Index '2' is out-of-bounds in tuple of length 2.
tuple[2] = 2;
官方文档的错误
从上面的例子可以看到,元组本身是元素类型和数量都确定的特殊数组,给已定义类型的元素赋值其他类型是不可以的,同时访问超出定义数量的元素也是不可以的,但是这里有个问题,那就是官方文档对于访问元组越界的元素描述:当访问一个越界的元素,会使用联合类型替代!
但是,经过我实际操作,就像我们上面的例子那样,访问元组越界的元素,是会报错的,甚至我将官网给出的例子,贴到官网提供的在线练习器里,也得到了一样的结果:
TIP
所以,结合元组的定义以及实际操作,我得出的结论就是:元组表示已知元素类型和数量的数组,不能访问超过长度的元素!
枚举
定义和用法
什么是枚举,官方说法是:
enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。
枚举采用enum
进行定义,使用时,采用枚举名字+成员名字进行取值,具体使用如下:
enum Direction {
Up,
Down,
Left,
Right,
}
const direction = Direction.Up;
默认情况
看下面的例子:
enum ErrorType {
INFO,
WARNING,
ERROR
}
const error: number = ErrorType.ERROR;
上面的例子大家可以看到,我定义了一个名为ErrorType
的枚举,用来表示错误的类型,从定义上看,它的成员分别是:INFO
、WARNING
、ERROR
,大家可以看到,我在下面定义了个变量error
,并且我给变量error
设定的类型为number
,是的,没错。官方的说法是:
枚举默认情况下,是从0开始为元素编号。
其表述的意思是:默认情况下,枚举的编号是从0开始递增的,大家可以理解成数组的索引。
这里说的元素编号其实就是每个枚举成员对应的索引,同时也是他的值,所以枚举成员INFO
、WARNING
、ERROR
实际的值其实是(0,1,2)
。在编译成JS
后,其编译的结果如下:
var ErrorType;
(function (ErrorType) {
ErrorType[ErrorType["INFO"] = 0] = "INFO";
ErrorType[ErrorType["WARNING"] = 1] = "WARNING";
ErrorType[ErrorType["ERROR"] = 2] = "ERROR";
})(ErrorType || (ErrorType = {}));
console.log(ErrorType); // => {0: 'INFO', 1: 'WARNING', 2: 'ERROR', INFO: 0, WARNING: 1, ERROR: 2}
const error = ErrorType.ERROR; // => 2
从上面的编译结果可以看出,我们在定义枚举时,如果不给他的成员另外赋值的情况下,实际是在定义一个JS
对象,其成员分别是:以枚举成员为key
,值分别为以0开始的数字,和以0开始的数值为key
,值为枚举成员。
反向映射
那么举一反三,上面的例子中,我们也可以这么去用,使用枚举值取获取枚举名称,也就是反向映射:
enum ErrorType {
INFO,
WARNING,
ERROR
}
// 以枚举成员为key,获取索引值,值为数字 2
const error: number = ErrorType.ERROR;
// 以枚索引值为key,获取值,值为字符串 'ERROR'
const error2: string = ErrorType[1];
console.log(error2 === 'ERROR') // => true
自定义枚举值
上面我们讲了默认情况下从0
开始为元素编号的枚举,除此之外,我们也可以自定义枚举值。
自定义索引开始值
默认情况下,索引值是从0
开始的,那么我们当然也可以自定义枚举的索引值从哪个数字开始:
enum ErrorType {
INFO = 3,
WARNING,
ERROR
}
console.log(ErrorType.ERROR); // => 5
其编译为JS
后,代码如下:
var ErrorType;
(function (ErrorType) {
ErrorType[ErrorType["INFO"] = 3] = "INFO";
ErrorType[ErrorType["WARNING"] = 4] = "WARNING";
ErrorType[ErrorType["ERROR"] = 5] = "ERROR";
})(ErrorType || (ErrorType = {}));
console.log(ErrorType); // => {3: 'INFO', 4: 'WARNING', 5: 'ERROR', INFO: 3, WARNING: 4, ERROR: 5}
当我们想要自定义枚举值的索引从哪个数字开始,只需要想上面的代码那样,在定义枚举值,给其第一个成员赋值即可。
TIP
需要注意的是,当我们定义的枚举有多个成员,且只给枚举第一个成员赋值时,那么只能给第一个成员赋值数字
类型,赋值其他类型的值就会报错。
enum ErrorType {
INFO = 'info',
WARNING, // => 枚举成员必须具有初始化表达式。
ERROR // => 枚举成员必须具有初始化表达式。
}
全自定义枚举值
所谓完全自定义枚举值,其实就是在定义枚举时,给他的每个成员都赋上值,看下面例子:
enum ErrorType {
INFO = 'info',
WARNING = 'warning',
ERROR = 'error',
}
// to JS => {INFO: 'info', WARNING: 'warning', ERROR: 'error'
enum State {
ERROR = 2,
INFO = 'info',
OPEN = 4
}
// to JS => {2: 'ERROR', 4: 'OPEN', ERROR: 2, INFO: 'info', OPEN: 4}
从上面的例子可以看到,我们在定义枚举时,是可以完全给枚举成员按照我们的需要进行赋值的,不过在赋值字符串类型和数字类型时,产生的结果上是不同的:
TIP
当我们自定义的枚举成员值为数字时,编译成JS对象后,会同时设置key
为数字,值为枚举成员字符串的属性和key
为枚举成员字符串,值为数字值的属性,即上面例子里对应的:2: 'ERROR'
, 4: 'OPEN'
, ERROR: 2
, OPEN: 4
;这种情况,我称之为反向枚举
但是,我们自定义的枚举成员值为字符串时,编译成JS对象后,则只会定义一个key
为枚举成员字符串,值为我们定义的值的属性,对应例子里的:INFO: 'info'
计算的和常量成员
当枚举值是默认的,或者是自定义的数字、字符串时,或者枚举成员使用 常量枚举表达式初始化。 常数枚举表达式是TypeScript表达式的子集,它可以在编译阶段求值。 当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:
- 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
- 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
- 带括号的常量枚举表达式
- 一元运算符 +, -, ~其中之一应用在了常量枚举表达式
- 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN或 Infinity,则会在编译阶段报错。
所有其它情况的枚举成员被当作是需要计算得出的值。简单理解就是编译阶段能够求出值的都算是常量成员,而需要代码运行时才能求出值的就是计算成员。
enum Test {
// 常量成员
File = 'name',
Number = 1,
Result = 2 + 3,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// 计算成员 => 需要运行时才能求出值
Length: [1,2,3].length
}
常量枚举
通常我们使用const
和常量枚举表达式来定义常量枚举,并且常量枚举会在编译后被删除,看下面的例子:
// 普通枚举
enum State1 {
Good,
Bad
}
const state1 = State1; // OK
console.log(State1); // OK => {0: 'Good', 1: 'Bad', Good: 0, Bad: 1}
上面的普通枚举编译结果为:
var State1;
(function (State1) {
State1[State1["Good"] = 0] = "Good";
State1[State1["Bad"] = 1] = "Bad";
})(State1 || (State1 = {}));
console.log(State1);
常量枚举:
// 常量枚举 -> const
const enum State2 {
Good,
Bad = 'bad'
}
const state2 = State2; // Error: "const" 枚举仅可在属性、索引访问表达式、导入声明的右侧、导出分配或类型查询中使用。
console.log(State2); // Error: "const" 枚举仅可在属性、索引访问表达式、导入声明的右侧、导出分配或类型查询中使用。
const state3 = State2.Good; // OK, to JS => const state3 = 0 /* State2.Good, */;
const state4 = [State2.Good, State2.Bad]; // OK, to JS => const state4 = [0 /* State2.Good */, 1 /* State2.Bad */]
其编译后的结果为:
const state3 = 0 /* State2.Good, */;
const state4 = [0 /* State2.Good */, 1 /* State2.Bad */]
常量枚举由于在编译后会将整个枚举定义部分删除,所以无法像上面那样直接将枚举整个赋值给变量,会引导报错,只能是将枚举成员值赋值给变量,且编译后得到的结果将会是直接把该枚举属性的值赋值给了变量,即const state3 = 0 /* State2.Good, */
。
外部枚举
外部枚举使用declare
关键字声明,它们通常表示存在于外部环境(例如其他 JavaScript
库或全局作用域)中的枚举类型。外部枚举的成员在没有初始化方法时,类型会被当做需要经过计算的成员,而不是常数成员。:
declare enum Enum {
A = 1,
B,
C = 2
}
外部枚举和内部枚举有个重要区别:
- 内部枚举:在内部枚举中,没有初始化方法的成员被当成常数成员,编译器会自动为它们分配递增的值,从 0 开始。
- 外部枚举:在外部枚举中,没有初始化方法的成员被当成计算成员,这意味着这些成员的值不能在编译时确定,而是需要在运行时计算。
declare enum State {
A = 1,
B,
C
}
// 在运行时,State.B 的值被计算为 2
console.log(State.A); // => 1
console.log(State.B); // 需要在运行时计算,输出: 2
console.log(State.C); // 需要在运行时计算,输出: 3
注意
通过上面的例子,基本已经能够掌握枚举的用法了,但是有几点需要注意的:
- 枚举值只能是字符串、数字,或计算结果为字符串、数字的表达式、或其他枚举值,否则会报错
enum Others {
NUMBER: 1,
STRING: 'hello',
TEST: true, // Error => Type 'boolean' is not assignable to type 'number' as required for computed enum member values.
ARRERY = [1, 2], // Error => Type 'number[]' is not assignable to type 'number' as required for computed enum member values.
SYMBOL = Symbol('test'), // Error => Type 'symbol' is not assignable to type 'number' as required for computed enum member values.
OBJECT = {test: ''} // Error => Type '{ test: string; }' is not assignable to type 'number' as required for computed enum member values.
}
- 在声明一个自定义枚举值时,当第一个成员被赋值为数字,第二个成员不赋值,使用默认递增的数字值,且第三个成员赋值为第一个成员的值 +1 的数字时,会覆盖第二个成员的数字值,但其反向映射值却会保留:
enum Enum {
A = 1,
B,
C = 2,
}
// to JS => {1: 'A', 2: 'C', A: 1, B: 2, C: 2}
Any
在TS
中,any
可以表示任何类型,但他真正的含义,其实是在告诉编译器,我们不希望类型检查器对该值进行检查而是直接让它们通过编译。
const a: any = 1;
const b: any = true;
// 当我们只知道部分类型时 -> 比如只知道是一个数组
const c: any[] = [];
Viod
在TS
中,void
表示没有任何类型,一般用在函数没有返回值时。另外,当我们指定一个变量为viod
时,几乎没有什么意义,因为此时,该变量的只能被赋值为:null
和undefined
。
TIP
注意,在严格模式也就是tsconfig.js
里strict
值为true
时,类型为void
的b变量,不能赋值为null
;
// 正常用法 - 表示函数没有返回任何类型的值
function test() :void {
alert(1);
}
const a: void = null;
const b: void = undefined;
null和 undefined
在TS
里,undefined和null两者各自有自己的类型分别叫做undefined和null。且默认情况下null和undefined是所有类型的子类型。 就是说你可以把 null和undefined赋值给number、string、boolean等类型的变量。但是当严格模式打开时,null和undefined只能赋值给void和它们各自。
const a: null = null;
const b: undefined = undefined;
const c: void = null;
const d: void = undefined;
// 非严格模式下 -> OK
const e: number = null;
const f: boolean = undefined;
Never
在TS
中,never
类型表示的是那些永不存在的值的类型。 例如, never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never
类型,当它们被永不为真的类型保护所约束时。
never
类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never
的子类型或可以赋值给never
类型(除了never
本身之外)。 即使 any
也不可以赋值给never
。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
Object
在 TS
中,object
类型用于表示非原始类型的值,即不是 number
、string
、boolean
、symbol
、null
或 undefined
的值。object
类型主要用来表示 JavaScript
中的对象,但它不提供对属性和方法的类型检查和自动补全。
let obj: object;
obj = { name: "Alice", age: 25 }; // 正确
obj = [1, 2, 3]; // 正确
obj = function() {}; // 正确
obj = "Hello"; // 错误: 不能将类型 'string' 分配给类型 'object'
obj = 42; // 错误: 不能将类型 'number' 分配给类型 'object'
obj = true; // 错误: 不能将类型 'boolean' 分配给类型 'object'
object 类型也可以用来约束 JavaScript 的 Object 类型。可以调用 Object 类型的内置方法,如 Object.keys、Object.values、Object.entries 等。
let obj: object = { name: "Alice", age: 25 };
console.log(Object.keys(obj)); // ['name', 'age']
console.log(Object.values(obj)); // ['Alice', 25]
console.log(Object.entries(obj)); // [['name', 'Alice'], ['age', 25]]
类型推断
类型推断是指有时候我们很确定某个变量是某个类型,清楚地知道一个实体具有比它现有类型更确切的类型。就可以通过类型断言来告诉编译器,不进行特殊的数据检查和解构。类型断言通常有两种写法:
- 使用
<类型>
的方式:
const other: any[] = ['hello', 'world'];
const test = <string[]>other;
- 使用
as
的方式:
const other: any[] = ['hello', 'world'];
const test = other as string[];
总结
今天通过对TS
官方文档的学习和自身的练习,基本掌握了基础类型:sting
、number
、数组
、元组
、枚举
、any
、void
、null
、undefined
、never
、object
等类型的语法和应用,也发现了一些平常不容易被发现的细节,都一一记录在上面的笔记中了,但咱们毕竟是初学,难免会有错误和遗漏,还请见谅。后面,我会继续学习TS
接下来的内容,并记录下来。
转载说明
本文允许全文转载,转载请注明来源: 平凡公子 - TS入门:基础类型