TypeScript入门实战笔记 -- 03 复杂基础类型:TypeScript 与 JavaScript 有何不同?
🍍数组
因为 TypeScript 的数组和元组转译为 JavaScript 后都是数组,所以这里我们把数组和元组这两个类型整合到一起介绍,便于理解。
数组类型(Array)
在 TypeScript 中,我们也可以像 JavaScript 一样定义数组类型,并且指定数组元素的类型。
首先,我们可以直接使用 [] 的形式定义数组类型,如下代码所示:
/** 子元素是数字类型的数组 */
let arrayOfNumber: number[] = [1, 2, 3];
/** 子元素是字符串类型的数组 */
let arrayOfString: string[] = ['x', 'y', 'z'];
同样,我们也可以使用 Array 泛型定义数组类型,如下代码所示:
/** 子元素是数字类型的数组 */
let arrayOfNumber: Array<number> = [1, 2, 3];
/** 子元素是字符串类型的数组 */
let arrayOfString: Array<string> = ['x', 'y', 'z'];
以上两种定义数组类型的方式虽然本质上没有任何区别,推荐使用 [] 这种形式来定义。一方面可以避免与 JSX 的语法冲突,另一方面可以减少不少代码量。
如果我们明确指定了数组元素的类型,以下所有操作都将因为不符合类型约定而提示错误。
let arrayOfNumber: number[] = ['x', 'y', 'z']; // 提示 ts(2322)
arrayOfNumber[3] = 'a'; // 提示 ts(2322)
arrayOfNumber.push('b'); // 提示 ts(2345)
let arrayOfString: string[] = [1, 2, 3]; // 提示 ts(2322)
arrayOfString[3] = 1; // 提示 ts(2322)
arrayOfString.push(2); // 提示 ts(2345)
元组类型(Tuple)
TypeScript 中的元组类型有以下优点:
- 混合类型存储:元组允许在同一个集合中存储不同类型的值,这是数组无法满足的需求,因为数组通常只存储相同类型的值。
- 固定数量和顺序:元组用于存储固定数量、不同类型的元素。这意味着你可以确保元素的类型和顺序始终保持一致。
- 函数参数:元组可以作为参数传递给函数。这使得你可以将一组有序的值作为一个整体传递。
- 解构赋值:你可以使用解构赋值来从元组中提取值。这使得你可以方便地将元组元素赋值给变量。
- 支持 Rest 参数和 Spread 表达式:从 TypeScript 3.0 开始,TypeScript 支持函数 Rest 参数和 Spread 表达式使用元组类型。
- 支持可选元素和 Rest 元素:元组类型允许在元素类型上后缀一个
?
来指定元素是可选的,同时也支持 Rest 元素。
元组最重要的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回。示例:
// 定义一个元组
let complexTuple: [string, number, boolean] = ['Runoob', 10, true];
// 访问元组元素
console.log(complexTuple[0]); // 输出 'Runoob'
console.log(complexTuple[1]); // 输出 10
console.log(complexTuple[2]); // 输出 true
// 更新元组元素
complexTuple[0] = 'Ws';
console.log(complexTuple[0]); // 输出 'Ws'
// 添加元素到元组
complexTuple.push(false);
console.log(complexTuple); // 输出 ['Bing', 10, true, false]
// 删除元组的最后一个元素
complexTuple.pop();
console.log(complexTuple); // 输出 ['Bing', 10, true]
// 元组解构
let [name, age, isStudent] = complexTuple;
console.log(name); // 输出 'Bing'
console.log(age); // 输出 10
console.log(isStudent); // 输出 true
上述代码执行环境配置:
1: tsc 03.Basic.2.ts --strict --alwaysStrict false --watch ( 执行转换为js操作 )
使用TypeScript编译器TSC来编译并监视03.Basic.2.ts文件的变化,同时启用所有的严格类型检查选项,但在编译后的JavaScript文件中不会强制使用use strict。
2:安装nodemon
( 全局安装npm install -g nodemon
) 检测.js文件变化重启项目,打印输出结果
🍍特殊类型
1. any
any 指的是一个任意类型,它是官方提供的一个选择性绕过静态类型检测的作弊方式。
我们可以对被注解为 any 类型的变量进行任何操作,包括获取事实上并不存在的属性、方法,并且 TypeScript 还无法检测其属性是否存在、类型是否正确。
比如我们可以把任何类型的值赋值给 any 类型的变量,也可以把 any 类型的值赋值给任意类型(除 never 以外)的变量,如下代码所示:
let anything: any = {};
anything.doAnything(); // 不会提示错误
anything = 1; // 不会提示错误
anything = 'x'; // 不会提示错误
let num: number = anything;?// 不会提示错误
let str: string = anything;?// 不会提示错误
如果我们不想花费过高的成本为复杂的数据添加类型注解,或者已经引入了缺少类型注解的第三方组件库,这时就可以把这些值全部注解为 any 类型,并告诉 TypeScript 选择性地忽略静态类型检测。尤其是在将一个基于 JavaScript 的应用改造成 TypeScript 的过程中,我们不得不借助 any 来选择性添加和忽略对某些 JavaScript 模块的静态类型检测,直至逐步替换掉所有的 JavaScript。any 类型会在对象的调用链中进行传导,即所有 any 类型的任意属性的类型都是 any,如下代码所示:
let anything: any = {};
let z = anything.x.y.z; // z 类型是 any,不会提示错误
z(); // 不会提示错误
这里我们需要明白且记住:Any is Hell(Any 是地狱)。
从长远来看,使用 any 绝对是一个坏习惯。如果一个 TypeScript 应用中充满了 any,此时静态类型检测基本起不到任何作用,也就是说与直接使用 JavaScript 没有任何区别。因此,除非有充足的理由,否则我们应该尽量避免使用 any ,并且开启禁用隐式 any 的设置。
2. unknown
unknown 是 TypeScript 3.0 中添加的一个类型,它主要用来描述类型并不确定的变量。
比如在多个 if else 条件分支场景下,它可以用来接收不同条件下类型各异的返回值的临时变量,如下代码所示:
let result: unknown;
if (x) {
result = x();
} else if (y) {
result = y();
}
在 3.0 以前的版本中,只有使用 any 才能满足这种动态类型场景。
与 any 不同的是,unknown 在类型上更安全。比如我们可以将任意类型的值赋值给 unknown,但 unknown 类型的值只能赋值给 unknown 或 any,如下代码所示:
let result: unknown;
let num: number = result; // 提示 ts(2322)
let anything: any = result; // 不会提示错误
使用 unknown 后,TypeScript 会对它做类型检测。但是,如果不缩小类型(Type Narrowing),我们对 unknown 执行的任何操作都会出现如下所示错误:
let result: unknown;
result.toFixed(); // 提示 ts(2571)
而所有的类型缩小手段对 unknown 都有效,如下代码所示:
let result: unknown;
if (typeof result === 'number') {
result.toFixed(); // 此处 hover result 提示类型是 number,不会提示错误
}
3. void、undefined、null
在TypeScript中,void
、undefined
和null
是三种不同的类型,它们的区别如下:
void
:在TypeScript中,void
表示该函数没有返回值。例如:
function noReturn(): void {
console.log("此函数没有返回值");
}
undefined
:undefined
是一个全局变量,表示未定义或未赋值3。如果变量声明了但未初始化,则该变量的默认值为undefined
。在函数中,如果你已声明但未返回任何值,则默认返回undefined
。null
:null
是一个表示无值或空值的JavaScript原始值。null
意味着对象不包含任何值。如果要显式设置变量或属性不含任何值,可以将其设置为null
。
在TypeScript中,null
和undefined
是所有类型的子类型,即可以赋值给任意类型1。但当我们在tsconfig.js
文件中设置strictNullChecks
为true
时,就不能将null
和undefined
赋值给除它们自身和void
之外的任意类型了。
总的来说,void
、undefined
和null
在TypeScript中有着不同的用途和语义,它们在编程中起着重要的作用。
如何在TypeScript中检查变量是否为null或undefined?
在TypeScript中,你可以使用以下几种方法来检查一个变量是否为null
或undefined
:
- 使用
==
或===
运算符:
if (variable == null) {
// 当变量为null或undefined时,这里的代码会被执行
}
- 使用
typeof
运算符:
if (typeof variable === 'undefined') {
// 当变量为undefined时,这里的代码会被执行
}
- 使用逻辑运算符:
if (!variable) {
// 当变量为null、undefined、NaN、空字符串''、0或false时,这里的代码会被执行
}
4. never
never 表示永远不会发生值的类型,这里我们举一个实际的场景进行说明。
首先,我们定义一个统一抛出错误的函数,代码示例如下
function ThrowError(msg: string): never {
throw Error(msg);
}
以上函数因为永远不会有返回值,所以它的返回值类型就是 never。
同样,如果函数代码中是一个死循环,那么这个函数的返回值类型也是 never,如下代码所示。
function InfiniteLoop(): never {
while (true) { }
}
never 是所有类型的子类型,它可以给所有类型赋值,如下代码所示。
let Unreachable: never = 1; // ts(2322)
Unreachable = 'string'; // ts(2322)
Unreachable = true; // ts(2322)
let num: number = Unreachable; // ok
let str: string = Unreachable;?// ok
let bool: boolean = Unreachable;?// ok
但是反过来,除了 never 自身以外,其他类型(包括 any 在内的类型)都不能为 never 类型赋值。
在恒为 false 的类型守卫条件判断下,变量的类型将缩小为 never(never 是所有其他类型的子类型,所以是类型缩小为 never,而不是变成 never)。因此,条件判断中的相关操作始终会报无法更正的错误(我们可以把这理解为一种基于静态类型检测的 Dead Code 检测机制),如下代码所示:
const str: string = 'string';
if (typeof str === 'number') {
str.toLowerCase(); // Property 'toLowerCase' does not exist on type 'never'.ts(2339)
}
基于 never 的特性,我们还可以使用 never 实现一些有意思的功能。比如我们可以把 never 作为接口类型下的属性类型,用来禁止写接口下特定的属性,示例代码如下:
const props: {
id: number,
name?: never
} = {
id: 1
}
props.name = null; // ts(2322))
props.name = 'str'; // ts(2322)
props.name = 1; // ts(2322)
此时,无论我们给 props.name 赋什么类型的值,它都会提示类型错误,实际效果等同于 name 只读 。
5. object
object 类型表示非原始类型的类型,即非?number、string、boolean、bigint、symbol、null、undefined 的类型。然而,它也是个没有什么用武之地的类型,如下所示的一个应用场景是用来表示 Object.create 的类型。
declare function create(o: object | null): any;
create({}); // ok
create(() => null); // ok
create(2); // ts(2345)
create('string'); // ts(2345)
🍍类型断言(Type Assertion)
TypeScript 类型检测无法做到绝对智能,毕竟程序不能像人一样思考。有时会碰到我们比 TypeScript 更清楚实际类型的情况,比如下面的例子:
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2); // 提示 ts(2322)
其中,greaterThan2 一定是一个数字(确切地讲是 3),因为 arrayNumber 中明显有大于 2 的成员,但静态类型对运行时的逻辑无能为力。
在 TypeScript 看来,greaterThan2 的类型既可能是数字,也可能是 undefined,所以上面的示例中提示了一个 ts(2322) 错误,此时我们不能把类型 undefined 分配给类型 number。
不过,我们可以使用一种笃定的方式——类型断言(类似仅作用在类型层面的强制类型转换)告诉 TypeScript 按照我们的方式做类型检查。
比如,我们可以使用 as 语法做类型断言,如下代码所示:
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2) as number;
又或者是使用尖括号 + 类型的格式做类型断言,如下代码所示:
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = <number>arrayNumber.find(num => num > 2);
以上两种方式虽然没有任何区别,但是尖括号格式会与 JSX 产生语法冲突,因此更推荐使用 as 语法。
注意:类型断言的操作对象必须满足某些约束关系,否则我们将得到一个 ts(2352) 错误,即从类型“源类型”到类型“目标类型”的转换是错误的,因为这两种类型不能充分重叠。
另外,在TypeScript中,any
和unknown
是两种特殊的类型。
any
类型:any
类型可以被赋予任何类型的值,也可以被断言为任何类型。这意味着,当一个变量被定义为any
类型时,你可以对它进行任何操作,而不会有类型检查错误。但是,这样做会放弃TypeScript的类型安全性,可能会导致运行时错误。unknown
类型:unknown
类型也可以被赋予任何类型的值,但在对其进行操作之前,必须先进行类型检查或类型断言。这意味着,你不能直接对unknown
类型的变量进行操作,除非你确定了它的具体类型。
因此,这句话的意思是,any
和unknown
类型在TypeScript中非常灵活,它们可以被赋予任何类型的值,也可以被断言为任何类型。但是,any
类型在使用时需要小心,因为它放弃了类型检查,而unknown
类型在使用时则需要进行额外的类型检查或类型断言,以确保类型安全。
我们除了可以把特定类型断言成符合约束添加的其他类型之外,还可以使用“字面量值 + as const”语法结构进行常量断言,具体示例如下所示:
/** str 类型是 '"str"' */
let str = 'str' as const;
/** readOnlyArr 类型是 'readonly [0, 1]' */
const readOnlyArr = [0, 1] as const;
在TypeScript中,any
和unknown
是两种特殊的类型。
any
类型:any
类型可以被赋予任何类型的值,也可以被断言为任何类型。这意味着,当一个变量被定义为any
类型时,你可以对它进行任何操作,而不会有类型检查错误。但是,这样做会放弃TypeScript的类型安全性,可能会导致运行时错误。unknown
类型:unknown
类型也可以被赋予任何类型的值,但在对其进行操作之前,必须先进行类型检查或类型断言。这意味着,你不能直接对unknown
类型的变量进行操作,除非你确定了它的具体类型。
因此,这句话的意思是,any
和unknown
类型在TypeScript中非常灵活,它们可以被赋予任何类型的值,也可以被断言为任何类型。但是,any
类型在使用时需要小心,因为它放弃了类型检查,而unknown
类型在使用时则需要进行额外的类型检查或类型断言,以确保类型安全。
此外还有一种特殊非空断言,即在值(变量、属性)的后边添加 '!' 断言操作符,它可以用来排除值为 null、undefined 的情况,具体示例如下:
let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531)
对于非空断言来说,我们同样应该把它视作和 any 一样危险的选择。
在复杂应用场景中,如果我们使用非空断言,就无法保证之前一定非空的值,比如页面中一定存在 id 为 feedback 的元素,数组中一定有满足 > 2 条件的数字,这些都不会被其他人改变。而一旦保证被改变,错误只会在运行环境中抛出,而静态类型检测是发现不了这些错误的。
let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!