目录
- [TypeScript 简介与安装](#TypeScript 简介与安装)
- 基础类型与类型注解
- 接口(Interface)
- 类(Class)
- 函数与函数类型
- 泛型(Generics)
- 高级类型
- 类型推断与类型守卫
- 装饰器(Decorators)
- 模块与命名空间
- 类型声明(
.d.ts)与第三方库 - [TypeScript 编译器配置(tsconfig.json)](#TypeScript 编译器配置(tsconfig.json))
- [与 React/Vue 的结合](#与 React/Vue 的结合)
- 实战:构建一个待办事项应用
- 进阶主题与最佳实践
1. TypeScript 简介与安装
1.1 什么是 TypeScript?
TypeScript 是 JavaScript 的静态类型超集,它编译为纯 JavaScript。它提供了类型系统、接口、泛型、装饰器等特性,能够在开发阶段捕捉潜在错误,提升代码质量和可维护性。
1.2 安装 TypeScript
全局安装 TypeScript 编译器:
bash
npm install -g typescript
检查版本:
bash
tsc --version
1.3 第一个 TypeScript 程序
创建 hello.ts:
typescript
function greet(name: string): string {
return `Hello, ${name}!`;
}
const message = greet("TypeScript");
console.log(message);
编译并运行:
bash
tsc hello.ts # 生成 hello.js
node hello.js # 输出: Hello, TypeScript!
2. 基础类型与类型注解
TypeScript 支持与 JavaScript 几乎相同的数据类型,并额外提供了枚举、元组等。
2.1 布尔值、数字、字符串
typescript
let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
2.2 数组
typescript
let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // 泛型写法
2.3 元组(Tuple)
元组允许表示一个已知元素数量和类型的数组。
typescript
let x: [string, number];
x = ["hello", 10]; // OK
// x = [10, "hello"]; // Error
2.4 枚举(Enum)
typescript
enum Color { Red, Green, Blue }
let c: Color = Color.Green;
// 手动赋值
enum Status { Success = 200, NotFound = 404 }
2.5 Any 与 Unknown
any:绕过类型检查,不推荐使用(除非迁移旧代码)。unknown:类型安全的 any,需要类型守卫才能使用。
typescript
let notSure: any = 4;
notSure = "maybe a string";
let value: unknown = "hello";
if (typeof value === "string") {
console.log(value.toUpperCase());
}
2.6 Void、Null、Undefined
typescript
function warnUser(): void {
console.log("This is a warning");
}
let u: undefined = undefined;
let n: null = null;
2.7 Never
never 表示永远不会有返回值的函数(如抛出异常或无限循环)。
typescript
function error(message: string): never {
throw new Error(message);
}
2.8 类型断言
typescript
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
// 或者尖括号语法(在 .tsx 中不可用)
let len: number = (<string>someValue).length;
3. 接口(Interface)
接口用来定义对象的结构,是 TypeScript 的核心设计原则之一。
3.1 基本接口
typescript
interface Person {
name: string;
age: number;
}
function greet(person: Person): string {
return `Hello, ${person.name}`;
}
3.2 可选属性与只读属性
typescript
interface Config {
readonly id: number;
name: string;
age?: number; // 可选
}
let config: Config = { id: 1, name: "Alice" };
// config.id = 2; // 错误,只读
3.3 函数类型接口
typescript
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function(src, sub) {
return src.indexOf(sub) !== -1;
};
3.4 可索引类型(Indexable Types)
typescript
interface StringArray {
[index: number]: string;
}
let arr: StringArray = ["a", "b"];
3.5 接口继承
typescript
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square: Square = { color: "red", sideLength: 10 };
4. 类(Class)
TypeScript 对 ES6 类进行了增强,增加了访问修饰符、抽象类等。
4.1 基本类
typescript
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number = 0): void {
console.log(`${this.name} moved ${distance}m.`);
}
}
4.2 访问修饰符
public:默认,任何地方可见。private:只能在类内部访问。protected:类内部和子类中可见。
typescript
class Person {
private ssn: string;
protected age: number;
public name: string;
constructor(name: string, age: number, ssn: string) {
this.name = name;
this.age = age;
this.ssn = ssn;
}
}
4.3 只读属性(readonly)
typescript
class Octopus {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
4.4 参数属性(Parameter Properties)
typescript
class Animal {
constructor(public name: string, private age: number) {}
// 自动创建并初始化 name 和 age 属性
}
4.5 继承(extends)
typescript
class Dog extends Animal {
breed: string;
constructor(name: string, age: number, breed: string) {
super(name, age);
this.breed = breed;
}
bark(): void {
console.log(`${this.name} barks!`);
}
}
4.6 抽象类(abstract)
typescript
abstract class Department {
constructor(public name: string) {}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
printMeeting(): void {
console.log(`Meeting for ${this.name}`);
}
}
5. 函数与函数类型
5.1 函数类型注解
typescript
// 完整写法
let myAdd: (x: number, y: number) => number = function(x, y) {
return x + y;
};
// 类型推断
let myAdd2 = (x: number, y: number): number => x + y;
5.2 可选参数与默认参数
typescript
function buildName(firstName: string, lastName?: string): string {
return lastName ? `${firstName} ${lastName}` : firstName;
}
function buildNameDefault(firstName: string, lastName = "Smith"): string {
return `${firstName} ${lastName}`;
}
5.3 剩余参数(Rest Parameters)
typescript
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
5.4 函数重载(Overloads)
TypeScript 允许为同一个函数定义多个类型签名。
typescript
function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
if (month !== undefined && day !== undefined) {
return new Date(yearOrTimestamp, month, day);
} else {
return new Date(yearOrTimestamp);
}
}
6. 泛型(Generics)
泛型允许创建可重用的组件,能够处理多种类型而不丢失类型信息。
6.1 泛型函数
typescript
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // 显式指定
let output2 = identity("myString"); // 类型推断
6.2 泛型接口
typescript
interface GenericIdentityFn<T> {
(arg: T): T;
}
let myIdentity: GenericIdentityFn<number> = identity;
6.3 泛型类
typescript
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGeneric = new GenericNumber<number>();
myGeneric.zeroValue = 0;
myGeneric.add = (x, y) => x + y;
6.4 泛型约束(Constraints)
使用 extends 限制泛型类型。
typescript
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK
logLength([1, 2, 3]); // OK
// logLength(123); // 错误,数字没有 length
6.5 在泛型中使用类型参数
typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
7. 高级类型
7.1 联合类型(Union Types)
typescript
let value: string | number;
value = "hello";
value = 42;
7.2 交叉类型(Intersection Types)
将多个类型合并为一个。
typescript
interface A { a: string }
interface B { b: number }
type C = A & B; // 同时拥有 a 和 b
7.3 类型别名(Type Aliases)
typescript
type Name = string;
type Point = { x: number; y: number };
7.4 字面量类型(Literal Types)
typescript
type Direction = "up" | "down" | "left" | "right";
let move: Direction = "up";
7.5 可辨识联合(Discriminated Unions)
typescript
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.sideLength ** 2;
}
}
7.6 映射类型(Mapped Types)
typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 使用内置工具类型
interface Person { name: string; age: number; }
type ReadonlyPerson = Readonly<Person>;
7.7 条件类型(Conditional Types)
typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<number>; // false
7.8 内置工具类型
Partial<T>:所有属性变为可选。Required<T>:所有属性变为必选。Readonly<T>:所有属性变为只读。Pick<T, K>:选取部分属性。Omit<T, K>:排除部分属性。Record<K, T>:构造一个对象类型。Exclude<T, U>:从 T 中排除可赋值给 U 的类型。ReturnType<T>:获取函数返回类型。
typescript
type User = { id: number; name: string; email: string };
type UserPreview = Pick<User, "id" | "name">;
type UserWithoutEmail = Omit<User, "email">;
8. 类型推断与类型守卫
8.1 类型推断
TypeScript 会自动推断变量类型:
typescript
let x = 3; // x 被推断为 number
8.2 类型守卫(Type Guards)
类型守卫用于在运行时缩小类型范围。
typeof 类型守卫
typescript
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
// 这里 strs 可能是数组或 null
}
}
instanceof 类型守卫
typescript
class Bird { fly() {} }
class Fish { swim() {} }
function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
animal.fly();
} else {
animal.swim();
}
}
自定义类型守卫(is)
typescript
function isFish(pet: Bird | Fish): pet is Fish {
return (pet as Fish).swim !== undefined;
}
in 操作符
typescript
if ("swim" in animal) {
// animal 是 Fish
}
8.3 断言函数(Assertion Functions)
typescript
function assertIsNumber(val: any): asserts val is number {
if (typeof val !== "number") {
throw new Error("Not a number");
}
}
9. 装饰器(Decorators)
装饰器是一种特殊声明,可以附加到类、方法、属性或参数上。注意 :装饰器目前是实验性特性,需要在 tsconfig.json 中启用 "experimentalDecorators": true。
9.1 类装饰器
typescript
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
9.2 方法装饰器
typescript
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
9.3 属性装饰器
typescript
function format(formatString: string) {
return function (target: any, propertyKey: string) {
let value = target[propertyKey];
const getter = function () {
return `${formatString} ${value}`;
};
Object.defineProperty(target, propertyKey, { get: getter });
};
}
9.4 参数装饰器
typescript
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
// 记录参数为必需的逻辑
}
10. 模块与命名空间
10.1 模块(Module)
TypeScript 遵循 ES6 模块规范。
typescript
// math.ts
export function add(x: number, y: number): number {
return x + y;
}
export const PI = 3.14;
// app.ts
import { add, PI } from "./math.js";
console.log(add(1, 2), PI);
10.2 默认导出
typescript
// utils.ts
export default function greet(name: string): string {
return `Hello ${name}`;
}
// app.ts
import greet from "./utils.js";
10.3 命名空间(Namespace)
命名空间是 TypeScript 早期用于组织代码的方式,现在推荐使用模块。
typescript
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
let validator = new Validation.LettersOnlyValidator();
11. 类型声明(.d.ts)与第三方库
11.1 类型声明文件
当使用 JavaScript 库时,需要提供类型声明。TypeScript 社区通过 DefinitelyTyped 提供大量库的类型定义。
安装类型定义(以 lodash 为例):
bash
npm install --save-dev @types/lodash
11.2 编写自己的 .d.ts
typescript
// myLib.d.ts
declare function myLib(name: string): string;
declare namespace myLib {
let version: string;
}
11.3 全局类型声明
typescript
// global.d.ts
declare const __APP_VERSION__: string;
12. TypeScript 编译器配置(tsconfig.json)
12.1 生成配置文件
bash
tsc --init
12.2 常用配置项详解
json
{
"compilerOptions": {
/* 基本选项 */
"target": "ES2020", // 编译目标
"module": "commonjs", // 模块系统
"lib": ["ES2020", "DOM"], // 包含的库文件
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 源码目录
"strict": true, // 启用所有严格类型检查
"esModuleInterop": true, // 允许 default 导入非模块
"skipLibCheck": true, // 跳过声明文件检查
"forceConsistentCasingInFileNames": true,
/* 额外检查 */
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
/* 装饰器 */
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
13. 与 React/Vue 的结合
13.1 TypeScript + React
使用 create-react-app 创建 TypeScript 项目:
bash
npx create-react-app my-app --template typescript
函数组件示例
tsx
// Greeting.tsx
interface GreetingProps {
name: string;
age?: number;
}
const Greeting: React.FC<GreetingProps> = ({ name, age }) => {
return <div>Hello {name}, age {age}</div>;
};
export default Greeting;
Hooks 类型
tsx
const [count, setCount] = useState<number>(0);
const inputRef = useRef<HTMLInputElement>(null);
13.2 TypeScript + Vue 3
使用 Vue CLI:
bash
vue create my-app
# 选择 TypeScript
组件示例(<script setup>)
vue
<script setup lang="ts">
interface Props {
title: string;
count?: number;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'update', value: number): void;
}>();
</script>
14. 实战:构建一个待办事项应用
我们将使用 TypeScript + 原生 DOM 操作,构建一个简单的 Todo 应用。
14.1 项目结构
todo-ts/
├── src/
│ ├── models/
│ │ └── Todo.ts
│ ├── services/
│ │ └── TodoService.ts
│ ├── ui/
│ │ └── TodoUI.ts
│ └── index.ts
├── index.html
├── tsconfig.json
└── package.json
14.2 定义 Todo 模型(models/Todo.ts)
typescript
export interface Todo {
id: number;
text: string;
completed: boolean;
}
14.3 服务层(services/TodoService.ts)
typescript
import { Todo } from "../models/Todo.js";
export class TodoService {
private todos: Todo[] = [];
private nextId = 1;
getTodos(): Todo[] {
return this.todos;
}
addTodo(text: string): void {
const newTodo: Todo = {
id: this.nextId++,
text,
completed: false,
};
this.todos.push(newTodo);
}
toggleTodo(id: number): void {
const todo = this.todos.find(t => t.id === id);
if (todo) todo.completed = !todo.completed;
}
deleteTodo(id: number): void {
this.todos = this.todos.filter(t => t.id !== id);
}
}
14.4 UI 层(ui/TodoUI.ts)
typescript
import { Todo } from "../models/Todo.js";
import { TodoService } from "../services/TodoService.js";
export class TodoUI {
private todoListElement: HTMLUListElement;
private inputElement: HTMLInputElement;
constructor(private todoService: TodoService) {
this.todoListElement = document.getElementById("todo-list") as HTMLUListElement;
this.inputElement = document.getElementById("todo-input") as HTMLInputElement;
this.bindEvents();
this.render();
}
private bindEvents(): void {
const addButton = document.getElementById("add-btn");
addButton?.addEventListener("click", () => this.addTodo());
this.inputElement.addEventListener("keypress", (e) => {
if (e.key === "Enter") this.addTodo();
});
}
private addTodo(): void {
const text = this.inputElement.value.trim();
if (text) {
this.todoService.addTodo(text);
this.inputElement.value = "";
this.render();
}
}
private toggleTodo(id: number): void {
this.todoService.toggleTodo(id);
this.render();
}
private deleteTodo(id: number): void {
this.todoService.deleteTodo(id);
this.render();
}
private render(): void {
const todos = this.todoService.getTodos();
this.todoListElement.innerHTML = "";
todos.forEach(todo => {
const li = document.createElement("li");
li.className = todo.completed ? "completed" : "";
li.innerHTML = `
<span>${todo.text}</span>
<button class="toggle-btn">✓</button>
<button class="delete-btn">✗</button>
`;
const toggleBtn = li.querySelector(".toggle-btn") as HTMLButtonElement;
const deleteBtn = li.querySelector(".delete-btn") as HTMLButtonElement;
toggleBtn.addEventListener("click", () => this.toggleTodo(todo.id));
deleteBtn.addEventListener("click", () => this.deleteTodo(todo.id));
this.todoListElement.appendChild(li);
});
}
}
14.5 入口文件(index.ts)
typescript
import { TodoService } from "./services/TodoService.js";
import { TodoUI } from "./ui/TodoUI.js";
const todoService = new TodoService();
const ui = new TodoUI(todoService);
14.6 HTML(index.html)
html
<!DOCTYPE html>
<html>
<head>
<title>TypeScript Todo</title>
<style>
.completed span { text-decoration: line-through; opacity: 0.6; }
</style>
</head>
<body>
<div>
<input type="text" id="todo-input" placeholder="Add a task..." />
<button id="add-btn">Add</button>
<ul id="todo-list"></ul>
</div>
<script type="module" src="./dist/index.js"></script>
</body>
</html>
14.7 编译与运行
配置 tsconfig.json 中 "module": "ESNext","outDir": "./dist"。
bash
tsc
# 然后打开 index.html 或使用 live server
15. 进阶主题与最佳实践
15.1 声明合并(Declaration Merging)
TypeScript 会将多个同名的接口、命名空间等合并。
typescript
interface Box {
height: number;
}
interface Box {
width: number;
}
// 最终 Box 拥有 height 和 width
15.2 符号(Symbols)
typescript
const uniqueKey = Symbol("key");
class MyClass {
[uniqueKey] = 123;
}
15.3 模板字面量类型(Template Literal Types)
typescript
type EventName = `on${Capitalize<string>}`;
type OnClick = EventName; // "onClick" 等
15.4 实用类型模式
递归 Partial
typescript
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
提取 Promise 内部类型
typescript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
15.5 性能与最佳实践
- 避免滥用
any:使用unknown替代,配合类型守卫。 - 启用严格模式 :
strict: true捕获更多错误。 - 使用
readonly防止意外修改。 - 利用类型推断,但复杂类型显式注解。
- 为函数和类编写 JSDoc 注释,配合类型系统。
- 使用
eslint+@typescript-eslint保持代码规范。
15.6 项目迁移策略
- 将
.js重命名为.ts。 - 设置
allowJs: true和checkJs: false逐步迁移。 - 添加
// @ts-nocheck到尚未修复的文件。 - 逐步修复类型错误,最终移除
any。