前面几篇我们一直在写 JavaScript。这篇是选读内容,来聊聊 TypeScript。
如果你看过前端项目的源码,会发现文件后缀经常是 .ts 而不是 .js,函数参数后面还跟着冒号和类型名,这就是 TypeScript。
本篇的 TypeScript 代码不能在网页上直接运行,你可以用 TypeScript Playground 在线尝试,也可以按照后面「编译和运行」一节的说明在本地运行。
TypeScript 是什么
一句话概括:TypeScript 就是带类型的 JavaScript。
它是 JavaScript 的超集,所有合法的 JavaScript 代码都是合法的 TypeScript 代码,TypeScript 只是在此基础上加了一套静态类型系统,让你可以给变量、函数参数和返回值标注类型。
为什么需要类型?看个例子:
JavaScript 不检查类型,add("1", "2") 不会报错,而是默默做了字符串拼接。这种 bug 在小脚本里无所谓,但在大型项目中排查起来很痛苦。
TypeScript 的做法是在编译阶段就拦住这类错误:
function add(a: number, b: number): number {
return a + b;
}
add(1, 2); // 3,没问题
add("1", "2"); // 编译错误!参数类型不匹配a: number 表示参数 a 必须是数字,括号后面的 : number 表示返回值也必须是数字。传入字符串时,TypeScript 编译器直接报错,不用等到运行时才发现。
换句话说,TypeScript 最终运行的还是 JavaScript,类型标注不会出现在最终代码里。TypeScript 编译器的作用就是在「TypeScript → JavaScript」这一步做类型检查,确保生成的 JavaScript 没有类型层面的意外问题,从而提高程序的可靠性。你可以把它理解成一个更严格的代码检查工具,帮你在写代码阶段就把潜在的 bug 揪出来。
基本类型标注
TypeScript 的类型标注语法很直观,在变量名或参数名后面加 : 类型 就行:
// 基本类型
let userName: string = "Alice";
let age: number = 25;
let isStudent: boolean = true;
// 数组
let scores: number[] = [90, 85, 92];
let names: string[] = ["Alice", "Bob"];函数的参数和返回值也可以标注类型。加 ? 表示参数是可选的:
function greet(name: string, title?: string): string {
if (title) {
return "Hello, " + title + " " + name;
}
return "Hello, " + name;
}
greet("Alice"); // "Hello, Alice"
greet("Alice", "Prof."); // "Hello, Prof. Alice"TypeScript 还有类型推断能力,很多时候不用手动标注,编译器能自动推断:
let x = 10; // 自动推断为 number
let s = "hello"; // 自动推断为 string
let arr = [1, 2, 3]; // 自动推断为 number[]所以实际写 TypeScript 时,不需要给每个变量都加类型。编译器能推断的就让它推断,只在必要的地方(函数参数、复杂对象)手动标注就行。
接口和类型别名
当你需要描述一个对象的结构时,可以用 interface:
interface User {
name: string;
age: number;
email?: string; // 可选属性
}
function printUser(user: User) {
console.log(user.name + ", " + user.age + " 岁");
}
printUser({ name: "Alice", age: 25 }); // OK
printUser({ name: "Bob", age: 30, email: "..." }); // OK
printUser({ name: "Charlie" }); // 错误!缺少 ageinterface 就像是给对象画了个「模板」,规定了必须有哪些属性、每个属性是什么类型。email?: string 中的 ? 表示这个属性可以没有。
type 关键字可以做类似的事情,还能定义联合类型:
// 联合类型:ID 可以是字符串或数字
type ID = string | number;
function findUser(id: ID) {
console.log("查找用户:" + id);
}
findUser(123); // OK
findUser("abc"); // OK
findUser(true); // 错误!简单来说,描述对象结构用 interface,定义联合类型或起别名用 type。
泛型基础
如果你学过 Java 或 C++ 的泛型,TypeScript 的泛型思路类似。没学过也没关系,看个例子就明白了。
假设你要写一个函数,返回数组的第一个元素:
// 写死类型,只能处理 number 数组
function firstNumber(arr: number[]): number {
return arr[0];
}
// 用泛型,什么类型的数组都能处理
function first<T>(arr: T[]): T {
return arr[0];
}
first([1, 2, 3]); // 返回类型是 number
first(["a", "b", "c"]); // 返回类型是 string<T> 是一个类型参数,调用时 TypeScript 会根据传入的参数自动推断 T 是什么类型。你不用显式写 first<number>([1, 2, 3]),编译器能自己推断出来。
编译和运行
TypeScript 在运行前需要先编译成 JavaScript(把类型标注去掉),传统做法是用 tsc(TypeScript Compiler):
# npm 是 Node.js 自带的包管理器,-g 表示全局安装
npm install -g typescript
# 编译,在同目录下生成 hello.js
tsc hello.ts
# 运行编译后的 JavaScript
node hello.js举个例子,下面这段 TypeScript 代码:
function add(a: number, b: number): number {
return a + b;
}
console.log(add(1, 2));编译后生成的 JavaScript:
function add(a, b) {
return a + b;
}
console.log(add(1, 2));所有类型标注都被移除了,剩下的就是普通的 JavaScript。TypeScript 的类型标注只在编译阶段起作用,运行时没有任何开销。
实际项目中,TypeScript 的编译配置写在项目根目录的 tsconfig.json 文件里:
{
"compilerOptions": {
"target": "ES2020", // 编译目标:生成 ES2020 语法的 JavaScript
"module": "commonjs", // 模块系统:使用 CommonJS(Node.js 默认)
"strict": true, // 开启严格类型检查
"outDir": "./dist" // 编译输出目录
},
"include": ["src/**/*"] // 要编译的源文件范围
}strict: true 开启严格模式,能最大程度发挥类型检查的作用,新项目建议都开启。有了这个配置文件,在项目根目录运行 tsc 就会按配置编译所有文件。
小结
TypeScript 本质上就是给 JavaScript 加了一层类型保护网。写代码时编译器能直接指出类型错误,IDE 的自动补全也更精准,不用猜一个变量到底是什么类型。
如果你之前写过 Java、C++ 或 Go,TypeScript 的类型系统会让你觉得很亲切。如果你一直写的是 Python 或 JavaScript,需要一点适应时间,但带来的好处是实实在在的。
目前 React、Vue、Next.js 这些主流的前端开发框架都原生支持 TypeScript,新项目基本都直接上 TypeScript 了。
至此 JavaScript 基础系列就全部讲完了。掌握了这些内容,你已经具备了用 JavaScript 刷题和做项目的基础,如果在项目中遇到 .ts 文件也不用发怵了。