TypeScript 教程:从入门到精通
目录
- 什么是 TypeScript?
- 为什么使用 TypeScript?
- 环境搭建
- 基础类型
- 原始类型
- 数组
- 元组
- 枚举
- Any & Unknown
- Void & Never & Null & Undefined
- Object
- 函数
- 函数声明与表达式
- 可选参数与默认参数
- 剩余参数
- 函数重载
- 接口
- 对象类型
- 可选属性
- 只读属性
- 函数类型
- 可索引类型
- 类类型实现
- 类
- 成员可见性:
public,private,protected - 存取器
- 静态成员
- 抽象类
- 成员可见性:
- 泛型
- 简单泛型
- 泛型接口
- 泛型类
- 泛型约束
- 类型推断与断言
- 类型推断
- 类型断言
- 非空断言
- 高级类型
- 交叉类型
- 联合类型
- 类型别名
- 类型守卫
- 映射类型
- 条件类型
- 项目配置与工具
tsconfig.json配置文件详解- 使用
tsc编译 - 与代码编辑器(如 VS Code)的集成
- 在项目中引入(React, Vue, Node.js)
- 学习资源与最佳实践
什么是 TypeScript?
TypeScript 是由微软开发的开源编程语言,它是 JavaScript 的一个超集。

- 超集:意味着任何合法的 JavaScript 代码也都是合法的 TypeScript 代码。
- 静态类型:TypeScript 在编译时进行类型检查,而不是在运行时,你可以在编写代码时就发现类型错误,而不是等到用户在浏览器中运行时才发现。
- 编译成 JavaScript:TypeScript 代码不能直接在浏览器或 Node.js 中运行,你需要使用 TypeScript 编译器(
tsc)将其编译成纯 JavaScript 代码。
TypeScript = JavaScript + 静态类型系统。
为什么使用 TypeScript?
使用 TypeScript 带来了诸多好处:
- 类型安全:在开发阶段就能捕获大量因类型不匹配导致的错误,减少了运行时崩溃的风险。
- 更好的代码提示和自动补全:现代编辑器(如 VS Code)可以根据类型信息提供极其智能的代码提示,提高开发效率。
- 提高代码可读性和可维护性:类型就像一份“活文档”,让代码的结构和意图更加清晰,便于团队协作和后期维护。
- 支持现代和未来的 JavaScript 特性:TypeScript 支持最新的 ECMAScript 标准,并可以编译成兼容旧环境的代码。
- 强大的重构工具:基于类型的重构(如重命名变量、修改函数签名)更加安全可靠。
环境搭建
-
安装 Node.js:TypeScript 依赖于 Node.js 环境,请确保你已经安装了 Node.js。
-
安装 TypeScript:通过 npm(Node 包管理器)全局安装 TypeScript。
(图片来源网络,侵删)npm install -g typescript
-
验证安装:
tsc --version
-
创建第一个项目:
# 创建一个新目录 mkdir my-ts-project cd my-ts-project # 初始化 npm 项目(会生成 package.json) npm init -y # 创建一个 TypeScript 配置文件 tsc --init
执行
tsc --init后,会生成一个tsconfig.json文件,这是 TypeScript 项目的配置核心。
基础类型
原始类型
// string let message: string = "Hello, TypeScript!"; // number (支持整数、浮点数、NaN、Infinity) let age: number = 25; // boolean let isActive: boolean = true; // null 和 undefined let notFound: null = null; let something: undefined = undefined; // 注意:默认情况下 null 和 undefined 是所有类型的子类型
数组
// 方式一:在元素类型后接 [] let list1: number[] = [1, 2, 3]; // 方式二:使用泛型 Array<元素类型> let list2: Array<string> = ["a", "b", "c"]; // 数组中也可以是联合类型 let list3: (number | string)[] = [1, "two", 3];
元组
元组类型允许表示一个已知元素数量和类型的数组。

// 定义一个包含 string 和 number 的元组 let x: [string, number]; // 初始化 x = ["hello", 10]; // 正确 // x = [10, "hello"]; // 错误:类型不匹配 // 访问元素 console.log(x[0].substring(0, 1)); // 正确 // console.log(x[1].substring(0, 1)); // 错误:number 类型没有 substring 方法
枚举
枚举是对 JavaScript 标准功能的一个补充,它允许你为一组数值赋予友好的名字。
// 数字枚举(默认从 0 开始)
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green; // c 的值是 1
// 也可以手动赋值
enum Status {
Pending = 1,
Success = 2,
Failed = 3,
}
// 字符串枚举
enum Message {
Success = "SUCCESS",
Error = "ERROR",
}
Any & Unknown
-
any: 允许你为任何类型赋值,也允许将任何类型赋给它,它会绕过 TypeScript 的类型检查,在你不确定类型时使用。let notSure: any = 4; notSure = "maybe a string"; notSure = false;
-
unknown: 是any的一个更安全的类型,任何类型的值都可以赋给unknown,unknown类型的值不能随意赋给其他类型,在使用前,你必须进行类型检查。let userInput: unknown; userInput = 5; userInput = "hello"; let userName: string; // userName = userInput; // 错误:不能将 unknown 类型赋给 string if (typeof userInput === "string") { userName = userInput; // 正确 }
Void & Never & Null & Undefined
-
void: 通常用于表示一个函数没有返回值。function logMessage(msg: string): void { console.log(msg); } -
never: 表示永远不会有返回值的函数,通常用于抛出异常或无限循环的函数。function error(message: string): never { throw new Error(message); } -
null和undefined: 和原始类型一样,它们也是有效的类型。
Object
object 类型表示非原始类型,也就是除 number, string, boolean, symbol, null 或 undefined 之外的类型。
let obj: object;
obj = { name: "Alice" };
// obj = 123; // 错误
// 更精确地定义对象类型,使用接口或类型别名(后面会讲)
let person: { name: string; age: number };
person = { name: "Bob", age: 30 };
函数
函数声明与表达式
// 函数声明
function add(x: number, y: number): number {
return x + y;
}
// 函数表达式
const myAdd = function(x: number, y: number): number {
return x + y;
};
// 箭头函数
const multiply = (x: number, y: number): number => {
return x * y;
};
可选参数与默认参数
// 可选参数必须在必选参数之后
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`;
}
return firstName;
}
console.log(buildName("John")); // John
console.log(buildName("John", "Doe")); // John Doe
// 默认参数
function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}
console.log(greet("Alice")); // Hello, Alice!
console.log(greet("Bob", "Hi")); // Hi, Bob!
剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((total, current) => total + current, 0);
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4)); // 10
函数重载
TypeScript 允许你定义多个函数签名,以便对同一个函数进行不同参数类型的调用。
// 定义重载签名
function processInput(value: string): string;
function processInput(value: number): number;
function processInput(value: boolean): boolean;
// 实现签名
function processInput(value: string | number | boolean): string | number | boolean {
if (typeof value === "string") {
return value.toUpperCase();
} else if (typeof value === "number") {
return value * 2;
} else {
return !value;
}
}
// 正确调用
let result1 = processInput("hello"); // string
let result2 = processInput(10); // number
let result3 = processInput(true); // boolean
接口
接口是命名对象类型的一种方式,它非常灵活。
对象类型
interface User {
name: string;
age: number;
}
function printUser(user: User) {
console.log(`User: ${user.name}, Age: ${user.age}`);
}
printUser({ name: "Charlie", age: 35 });
可选属性
interface Car {
brand: string;
model: string;
year?: number; // 可选属性
}
const myCar: Car = { brand: "Toyota", model: "Camry" };
const anotherCar: Car = { brand: "Honda", model: "Civic", year: 2025 };
只读属性
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // 错误:无法分配到 "x" ,因为它是只读属性
函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
return src.indexOf(sub) > -1;
};
可索引类型
// 支持数字索引
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
// 支持字符串索引
interface StringDictionary {
[key: string]: number;
}
let myDict: StringDictionary = { age: 25, score: 98 };
类类型实现
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
}
类
TypeScript 为 ES6 的类添加了类型声明和一些新的特性。
成员可见性:public, private, protected
public(默认): 成员在类内部和外部都可以访问。private: 成员只能在类内部访问。protected: 成员只能在类内部和其子类中访问。
class Animal {
public name: string; // 可以省略 public
private age: number;
protected species: string;
constructor(name: string, age: number, species: string) {
this.name = name;
this.age = age;
this.species = species;
}
public makeSound() {
console.log(`${this.name} makes a sound.`);
}
private getAge() {
return this.age;
}
}
class Dog extends Animal {
constructor(name: string, age: number) {
super(name, age, "Canis lupus");
}
public bark() {
// this.age; // 错误:private 属性不能在子类中访问
this.species; // 正确:protected 属性可以在子类中访问
console.log(`${this.name} barks!`);
}
}
const myDog = new Dog("Rex", 5);
myDog.makeSound(); // Rex makes a sound.
myDog.bark(); // Rex barks!
// myDog.getAge(); // 错误:private 方法不能在外部访问
存取器
使用 get 和 set 来拦截对对象属性的访问。
class Employee {
private _fullName: string = "";
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (newName.length > 0) {
this._fullName = newName;
} else {
throw new Error("Name cannot be empty");
}
}
}
let employee = new Employee();
employee.fullName = "Jane Doe";
console.log(employee.fullName); // Jane Doe
// employee.fullName = ""; // 抛出错误
静态成员
使用 static 关键字定义属于类本身而不是类实例的成员。
class MathUtils {
static PI: number = 3.14159;
static calculateCircleArea(radius: number): number {
return this.PI * radius * radius;
}
}
// console.log(MathUtils.PI);
// console.log(MathUtils.calculateCircleArea(5));
抽象类
抽象类作为其他类的基类使用,不能被实例化,它可能包含未实现的方法(抽象方法)。
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // 必须在子类中实现
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // 在派生类构造函数中必须调用 super()
}
printMeeting(): void {
console.log("The Accounting Department meets at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
// let department = new Department(); // 错误:不能创建抽象类的实例
let department = new AccountingDepartment();
department.printName(); // Department name: Accounting and Auditing
department.printMeeting(); // The Accounting Department meets at 10am.
department.generateReports(); // Generating accounting reports...
泛型
泛型是一种在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再指定类型的特性,它提供了可重用性、类型安全和灵活性。
简单泛型
// 创建一个 identity 函数,它返回任何传入的类型
function identity<T>(arg: T): T {
return arg;
}
// 使用方式1:明确指定类型
let output1 = identity<string>("myString");
let output2 = identity(42); // 使用方式2:类型推断,T 被推断为 number
console.log(output1); // myString
console.log(output2); // 42
泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
let myIdentity: GenericIdentityFn<number> = identity;
泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zero: T, addFn: (x: T, y: T) => T) {
this.zeroValue = zero;
this.add = addFn;
}
}
let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);
console.log(myGenericNumber.add(5, 10)); // 15
let stringNumeric = new GenericNumber<string>("", (x, y) => x + y);
console.log(stringNumeric.add("hello, ", "world")); // hello, world
泛型约束
有时,我们希望泛型类型 T 拥有某些属性,这时可以使用 extends 关键字进行约束。
// 约束 T 必须有 length 属性
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在我们知道 arg 有 .length 属性
return arg;
}
loggingIdentity("Hello"); // 正确,string 有 length
loggingIdentity([1, 2, 3]); // 正确,array 有 length
// loggingIdentity(3); // 错误,number 没有 length 属性
类型推断与断言
类型推断
TypeScript 编译器会根据你提供的代码自动推断出类型,这减少了显式类型注解的需要。
let x = 3; // x 被推断为 number
let y = [1, "two", 3]; // y 被推断为 (number | string)[]
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
let first = getFirstElement([1, 2, 3]); // first 被推断为 number | undefined
类型断言
类型断言告诉编译器“我知道我在做什么,相信我,这个值是这个类型”,它不会进行数据转换或运行时检查,只是欺骗编译器。
语法1:<Type>value
语法2:value as Type (在 JSX 中必须使用这个)
let someValue: any = "this is a string"; // 方式1 let strLength1 = (<string>someValue).length; // 方式2 (推荐,尤其在 TypeScript 3.7+ 和 React JSX 中) let strLength2 = (someValue as string).length;
非空断言
在可能为 null 或 undefined 的值后面使用 ,告诉 TypeScript 这个值不可能是 null 或 undefined。
function getNonNullElement<T>(arr: T[], index: number): T {
// 如果不使用 !,TypeScript 会报错,因为 index 可能越界
return arr[index]!;
}
const myArray = [1, 2, 3];
const firstElement = getNonNullElement(myArray, 0); // 1
高级类型
交叉类型
将多个类型合并成一个类型,常用于将多个对象的属性合并。
interface Person {
name: string;
}
interface Loggable {
log: () => void;
}
type PersonWithLog = Person & Loggable;
let p: PersonWithLog;
p = {
name: "Alice",
log: () => console.log("Logging..."),
};
联合类型
一个值可以是几种类型之一。
function printId(id: number | string) {
console.log(id);
}
printId(101);
printId("202");
类型别名
使用 type 关键字给一个类型起个新名字,它和接口很像,但有一些区别(比如可以联合、交叉)。
type ID = number | string;
type User = {
id: ID;
name: string;
};
let user: User = { id: 123, name: "Bob" };
let anotherUser: User = { id: "abc-123", name: "Charlie" };
类型守卫
一种在运行时检查表达式类型的机制,以确保在某个代码块中,变量的类型是更具体的类型。
// typeof 守卫
function printValue(x: number | string) {
if (typeof x === "string") {
console.log(x.toUpperCase()); // 在这个块里,x 被视为 string
} else {
console.log(x.toFixed(2)); // 在这个块里,x 被视为 number
}
}
// instanceof 守卫
class Dog {}
class Cat {}
function getPet(pet: Dog | Cat) {
if (pet instanceof Dog) {
console.log("It's a dog");
} else {
console.log("It's a cat");
}
}
映射类型
基于一个现有类型,创建一个新类型,其属性是原类型的转换,将所有属性变为只读。
interface Person {
name: string;
age: number;
}
// 将 Person 的所有属性变为可选
type PartialPerson = {
[P in keyof Person]?: Person[P];
};
// 将 Person 的所有属性变为只读
type ReadonlyPerson = {
readonly [P in keyof Person]: Person[P];
};
const readOnlyPerson: ReadonlyPerson = { name: "David", age: 40 };
// readOnlyPerson.name = "Dave"; // 错误
条件类型
类似于 JavaScript 中的三元运算符,但作用于类型。
type ExtractType<T, U> = T extends U ? T : never; type A = ExtractType<string | number | boolean, string>; // string type B = ExtractType<string | number | boolean, number>; // number type C = ExtractType<string | number | boolean, boolean>; // boolean type D = ExtractType<string | number | boolean, Date>; // never // 一个实际应用:NonNullable 类型 type NonNullable<T> = T extends null | undefined ? never : T; type E = NonNullable<string | null | undefined>; // string
项目配置与工具
tsconfig.json 配置文件
这是 TypeScript 项目的灵魂,下面是一些常用选项:
{
"compilerOptions": {
// 目标 JavaScript 版本
"target": "es2025", // 指定 ECMAScript 目标版本
"module": "commonjs", // 指定模块代码生成方式
"lib": ["es2025"], // 指定要包含在编译中的库文件
// 模块解析策略
"moduleResolution": "node", // 模块解析策略
"baseUrl": "./", // 用于解析非相对模块名称的基本目录
"paths": { // 模块名到基于 baseUrl 的路径映射的列表
"@/*": ["src/*"]
},
// 严格类型检查
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any 类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式的值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 总是以严格模式解析并为每个源文件生成 "use strict" 语句
// 额外检查
"noUnusedLocals": true, // 若有未使用的局部变量则报错
"noUnusedParameters": true, // 若有未使用的参数则报错
"noImplicitReturns": true, // 不是函数的所有返回路径都有返回值时报错
"noFallthroughCasesInSwitch": true, // switch 语句必须有 default
// 源码映射
"sourceMap": true, // 生成相应的 '.map' 文件
// 输出目录
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 仅用来控制输出目录结构 - 输出目录会 mirrors 此处
// 高级
"esModuleInterop": true, // 启用 emitDecoratorMetadata
"skipLibCheck": true, // 跳过对声明文件的类型检查
"forceConsistentCasingInFileNames": true // 禁止对同一文件使用不一致的大小写引用
},
"include": ["src/**/*"], // 指定要编译的文件
"exclude": ["node_modules"] // 指定不编译的文件
}
使用 tsc 编译
在项目根目录下运行:
# 使用 tsconfig.json 的配置进行编译 tsc # 监听模式,文件变化时自动重新编译 tsc --watch
与代码编辑器(如 VS Code)的集成
VS Code 对 TypeScript 有第一流的支持,只要你安装了 TypeScript 插件,它就能:
- 实时显示类型错误。
- 提供强大的代码提示和自动补全。
- 快速跳转到定义。
- 重构代码。
在项目中引入
-
React:
npm install --save-dev typescript @types/react @types/react-dom- 将
.js文件重命名为.tsx(如果使用了 JSX)。 - 配置
tsconfig.json。
-
Vue:
npm install --save-dev typescript vue-tsc @vue/tsconfig- 安装
Volar插件(替代 Vetur)。 - 配置
tsconfig.json。
-
Node.js:
npm install --save-dev typescript @types/node- 配置
tsconfig.json,target可以是es2025或更高,module通常设为commonjs。
学习资源与最佳实践
学习资源
- 官方文档: TypeScript 官方文档 (最好的资源,最权威)
- TypeScript Deep Dive: 一本开源的 TypeScript 深入浅出书籍
- React + TypeScript Cheatsheets: React TypeScript Cheatsheet
最佳实践
- 开启
strict模式:这是最有效的实践,它能帮你发现最多的潜在问题。 - 优先使用
interface而不是type:对于对象类型,interface更易于扩展和合并。type更适合联合、交叉等复杂类型。 - 尽可能使用
const和let:避免使用var。 - 避免使用
any:除非你真的需要绕过类型检查。unknown是一个更安全的选择。 - 利用类型推断:不要过度使用类型注解,让 TypeScript 帮你做。
- 保持类型简单和明确:清晰的类型定义是代码可维护性的关键。
- 为第三方库安装类型定义:使用
npm install --save-dev @types/库名。
希望这份教程能帮助你顺利入门 TypeScript!祝你学习愉快!
