各位观众老爷们,大家好!我是你们的老朋友,今天要跟大家唠唠嗑,聊聊 TypeScript 里的几位重要人物:类型推断、控制流分析,还有声明文件这哥仨。 咱们的目标是,让大家听完之后,不仅知道它们是啥,还能在实际工作中玩转它们。准备好了吗?咱们这就开讲!
第一幕:类型推断——“它猜你心里想什么”
类型推断,英文名叫 Type Inference,听起来是不是很唬人?其实简单来说,就是 TypeScript 编译器“猜”你的变量、表达式的类型。它不用你显式地告诉它,自己就能分析出来。这就像是你跟你的老朋友,一个眼神,对方就知道你要干啥。
1.1 基础类型推断
最简单的例子:
let message = "Hello, TypeScript!"; // TypeScript 推断 message 的类型为 string
// message = 123; // 报错:不能将类型“number”分配给类型“string”
你看,咱们没写 let message: string = "Hello, TypeScript!";
,TypeScript 也知道 message
是字符串类型。这就是类型推断的威力!
再来一个数字的例子:
let count = 10; // TypeScript 推断 count 的类型为 number
// count = "abc"; // 报错:不能将类型“string”分配给类型“number”
1.2 函数返回值类型推断
函数返回值也可以被推断:
function add(a: number, b: number) {
return a + b; // TypeScript 推断函数的返回类型为 number
}
let result = add(5, 3); // result 的类型为 number
function greet(name: string) {
console.log(`Hello, ${name}!`);
// 没有显式返回值,TypeScript 推断返回类型为 void
}
let greeting = greet("Alice"); // greeting 的类型为 void
注意,如果函数没有 return
语句,或者 return
语句没有返回值,TypeScript 会推断返回类型为 void
。
1.3 最佳通用类型推断
当从几个表达式中推断类型时,会使用这些表达式的“最佳通用类型”。 比如:
let arr = [1, 2, null]; // TypeScript 推断 arr 的类型为 (number | null)[]
TypeScript 会找到一个所有类型都兼容的类型。 在这个例子中,number
和 null
的最佳通用类型是 number | null
。
1.4 上下文类型推断
有时候,类型推断会依赖于上下文。
window.addEventListener("click", function(event) {
// TypeScript 知道 event 是 MouseEvent 类型
console.log(event.clientX, event.clientY);
});
在这个例子中,addEventListener
的第一个参数 "click"
告诉 TypeScript,回调函数的 event
参数应该是 MouseEvent
类型。
1.5 技巧:利用类型推断写出更简洁的代码
类型推断可以让我们少写很多类型注解,让代码更简洁。但要注意,过度依赖类型推断可能会降低代码的可读性。所以,要在简洁和可读性之间找到平衡。
第二幕:控制流分析——“程序执行到哪儿,它都知道”
控制流分析,英文名叫 Control Flow Analysis,是 TypeScript 编译器分析程序执行路径的一种技术。它能根据 if
、else
、switch
、循环等语句,推断出变量在不同代码分支中的类型。
2.1 可辨识联合类型
可辨识联合类型(Discriminated Unions)是控制流分析的一个重要应用场景。 想象一下,你有一个形状的类型,它可以是圆形、矩形或三角形。
interface Circle {
kind: "circle";
radius: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Triangle {
kind: "triangle";
base: number;
height: number;
}
type Shape = Circle | Rectangle | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius; // TypeScript 知道 shape 是 Circle 类型
case "rectangle":
return shape.width * shape.height; // TypeScript 知道 shape 是 Rectangle 类型
case "triangle":
return 0.5 * shape.base * shape.height; // TypeScript 知道 shape 是 Triangle 类型
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
在这个例子中,shape.kind
就是一个辨识属性。 TypeScript 根据 shape.kind
的值,就能确定 shape
的具体类型。在每个 case
语句中,TypeScript 都能正确地推断出 shape
的类型,并进行类型检查。
2.2 类型收窄
类型收窄 (Type Narrowing) 是控制流分析的另一个关键概念。 它是指在代码块中,根据某些条件,将变量的类型从一个联合类型收窄到一个更具体的类型。
function printLength(str: string | null) {
if (str) {
// TypeScript 知道 str 在这里是 string 类型
console.log(str.length);
} else {
// TypeScript 知道 str 在这里是 null 类型
console.log("String is null");
}
}
在这个例子中,if (str)
这个条件语句就把 str
的类型从 string | null
收窄到了 string
。
2.3 instanceof
操作符
instanceof
操作符也可以用于类型收窄。
class Animal {
move() {
console.log("Moving...");
}
}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
function doSomething(animal: Animal) {
if (animal instanceof Dog) {
// TypeScript 知道 animal 在这里是 Dog 类型
animal.bark();
} else {
animal.move();
}
}
2.4 自定义类型保护
有时候,TypeScript 无法自动推断类型,我们需要自定义类型保护函数来帮助它。
function isNumber(x: any): x is number {
return typeof x === "number";
}
function printDouble(x: number | string) {
if (isNumber(x)) {
// TypeScript 知道 x 在这里是 number 类型
console.log(x * 2);
} else {
// TypeScript 知道 x 在这里是 string 类型
console.log(x.toUpperCase());
}
}
isNumber(x): x is number
就是一个类型保护函数。 它的返回值类型 x is number
告诉 TypeScript,如果 isNumber(x)
返回 true
,那么 x
就是 number
类型。
2.5 技巧:善用控制流分析,写出更健壮的代码
控制流分析可以帮助我们发现代码中的潜在问题,并提高代码的健壮性。 比如,它可以帮助我们避免访问 null
或 undefined
类型的属性,或者在不正确的类型上调用方法。
第三幕:声明文件 (.d.ts) ——“给 JavaScript 代码配个翻译”
声明文件(Declaration Files),扩展名为 .d.ts
,是用来描述 JavaScript 代码类型信息的文件。 它们不包含任何可执行代码,只包含类型声明。
3.1 为什么需要声明文件?
因为 JavaScript 是一种动态类型语言,没有类型信息。 当 TypeScript 代码需要使用 JavaScript 代码时,就需要声明文件来告诉 TypeScript 这些 JavaScript 代码的类型信息。 就像是给 JavaScript 代码配了个翻译,让 TypeScript 能够理解它。
3.2 如何生成声明文件?
-
手动编写: 对于简单的 JavaScript 库,可以手动编写声明文件。
-
使用
tsc
编译器: 可以通过tsc
编译器的--declaration
选项自动生成声明文件。tsc --declaration index.ts
这条命令会生成一个
index.d.ts
文件,其中包含了index.ts
中导出的类型声明。 -
使用第三方工具: 有一些第三方工具可以帮助生成声明文件,比如
dts-gen
。
3.3 声明文件的内容
声明文件主要包含以下内容:
- 类型声明: 使用
type
或interface
关键字声明类型。 - 变量声明: 使用
declare let
、declare const
或declare var
关键字声明变量。 - 函数声明: 使用
declare function
关键字声明函数。 - 类声明: 使用
declare class
关键字声明类。 - 模块声明: 使用
declare module
关键字声明模块。
3.4 声明文件的例子
假设我们有一个 JavaScript 文件 utils.js
:
// utils.js
function add(a, b) {
return a + b;
}
const PI = 3.14159;
module.exports = {
add,
PI
};
我们可以为它编写一个声明文件 utils.d.ts
:
// utils.d.ts
declare module "utils" {
function add(a: number, b: number): number;
const PI: number;
}
或者,如果使用 ES 模块:
// utils.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
对应的声明文件 utils.d.ts
:
// utils.d.ts
export function add(a: number, b: number): number;
export const PI: number;
3.5 如何使用声明文件?
-
与 JavaScript 文件放在同一目录下: TypeScript 编译器会自动查找与 JavaScript 文件同名的声明文件。
-
使用
/// <reference types="..."/>
指令: 可以在 TypeScript 文件中使用/// <reference types="..."/>
指令来引入声明文件。/// <reference types="./utils" /> import { add, PI } from "./utils"; console.log(add(1, 2)); console.log(PI);
-
在
tsconfig.json
文件中配置typeRoots
或types
选项: 可以指定 TypeScript 编译器查找声明文件的目录。{ "compilerOptions": { "typeRoots": ["./typings"] // 指定声明文件所在的目录 } }
3.6 DefinitelyTyped
DefinitelyTyped 是一个大型的 TypeScript 声明文件仓库,包含了许多流行的 JavaScript 库的声明文件。 我们可以使用 npm install @types/<package-name>
命令来安装这些声明文件。
例如,要安装 Lodash 的声明文件:
npm install @types/lodash
3.7 技巧:编写高质量的声明文件,让你的 JavaScript 代码更易于使用
编写高质量的声明文件可以提高 JavaScript 代码的可重用性和可维护性。 在编写声明文件时,要尽可能地提供准确、完整的类型信息。
总结
今天咱们聊了 TypeScript 的三个重要特性:类型推断、控制流分析和声明文件。
特性 | 作用 | 优点 | 缺点 |
---|---|---|---|
类型推断 | 自动推断变量、表达式的类型 | 减少代码中的类型注解,提高代码简洁性 | 过度依赖类型推断可能会降低代码的可读性,需要权衡 |
控制流分析 | 分析程序执行路径,推断变量在不同代码分支中的类型 | 提高代码的健壮性,避免潜在的类型错误 | 对于复杂的控制流,TypeScript 可能无法正确推断类型,需要手动进行类型收窄 |
声明文件 (.d.ts) | 描述 JavaScript 代码的类型信息,让 TypeScript 能够理解 JavaScript 代码 | 允许 TypeScript 代码使用 JavaScript 代码,提高代码的可重用性和可维护性 | 编写声明文件需要额外的工作量,需要保证声明文件的准确性和完整性 |
希望今天的讲座能帮助大家更好地理解和使用 TypeScript。 记住,实践是检验真理的唯一标准。 多写代码,多思考,才能真正掌握这些知识。
好了,今天的讲座就到这里。 谢谢大家! 咱们下回再见!