返回文章列表

TypeScript 基础教程:为 JavaScript 添加类型安全

TypeScript 是由微软开发的开源编程语言,它是 JavaScript 的超集,在 JavaScript 的基础上添加了静态类型系统。TypeScript 代码在编译时会进行类型检查,并最终转换为纯 JavaScript 代码,可以在任何支持 JavaScript 的环境中运行。本文将从零开始带你学习 TypeScript 的核心概念,掌握类型系统的精髓。

一、TypeScript 简介与安装

1.1 为什么需要 TypeScript

JavaScript 是一种动态类型语言,变量的类型在运行时才能确定。这带来了一些问题:

TypeScript 通过静态类型系统解决了这些问题:

1.2 安装 TypeScript

# 全局安装 TypeScript 编译器
npm install -g typescript

# 验证安装
tsc --version

# 也可以在项目中本地安装
npm install --save-dev typescript

# 初始化 TypeScript 配置文件
tsc --init

1.3 第一个 TypeScript 程序

// hello.ts
function greet(name: string): string {
    return `Hello, ${name}!`;
}

const message: string = greet('平平');
console.log(message);
# 编译为 JavaScript
tsc hello.ts

# 编译后会生成 hello.js,然后运行
node hello.js
# 输出: Hello, 平平!

1.4 tsconfig.json 配置

{
    "compilerOptions": {
        "target": "ES2020",              // 编译目标 JS 版本
        "module": "commonjs",            // 模块系统
        "strict": true,                  // 启用所有严格类型检查
        "esModuleInterop": true,         // 允许 ES 模块互操作
        "skipLibCheck": true,            // 跳过类型库检查
        "forceConsistentCasingInFileNames": true,
        "outDir": "./dist",              // 输出目录
        "rootDir": "./src",              // 源代码目录
        "declaration": true,             // 生成 .d.ts 声明文件
        "sourceMap": true                // 生成 source map
    },
    "include": ["src/**/*"],             // 包含的文件
    "exclude": ["node_modules", "dist"]  // 排除的文件
}

二、基本类型

2.1 原始类型

// 布尔值
let isDone: boolean = false;

// 数字(包括整数和浮点数)
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;

// 字符串
let color: string = 'blue';
let fullName: string = `平平`;
let sentence: string = `Hello, my name is ${fullName}.`;

// null 和 undefined
let u: undefined = undefined;
let n: null = null;

// symbol(ES6)
let sym: symbol = Symbol('key');

2.2 数组

// 第一种写法:类型 + []
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: string[] = ['a', 'b', 'c'];

// 第二种写法:泛型数组
let list: Array<number> = [1, 2, 3];
let names: Array<string> = ['Alice', 'Bob'];

// 只读数组
let readOnlyArray: ReadonlyArray<number> = [1, 2, 3];
// readOnlyArray[0] = 4;  // 报错:只读数组不能修改

2.3 元组(Tuple)

元组表示已知元素数量和类型的数组,各元素类型可以不同:

// 定义元组:[string, number]
let person: [string, number] = ['平平', 25];

console.log(person[0]); // '平平'
console.log(person[1]); // 25

// person[0] = 123;  // 报错:必须是 string
// person = ['平平']; // 报错:缺少 number 元素

// 可选元素的元组
let optionalTuple: [string, number?] = ['Alice'];
optionalTuple = ['Bob', 30];

2.4 枚举(Enum)

// 数字枚举(默认从 0 开始)
enum Color {
    Red,      // 0
    Green,    // 1
    Blue      // 2
}
let c: Color = Color.Green;
console.log(c); // 1

// 自定义起始值
enum Direction {
    Up = 1,
    Down,    // 2
    Left,    // 3
    Right    // 4
}

// 字符串枚举
enum Status {
    Pending = 'PENDING',
    Success = 'SUCCESS',
    Failed = 'FAILED'
}
let status: Status = Status.Success;
console.log(status); // 'SUCCESS'

// 反向映射(仅数字枚举)
enum Weekday {
    Monday = 1,
    Tuesday,
    Wednesday
}
console.log(Weekday[1]); // 'Monday'
console.log(Weekday.Monday); // 1

// const 枚举(编译时内联,性能更好)
const enum LogLevel {
    Debug,
    Info,
    Warning,
    Error
}
const level: LogLevel = LogLevel.Info; // 编译后直接是 1

2.5 any、unknown、void、never

// any:任意类型(放弃类型检查,不推荐使用)
let notSure: any = 4;
notSure = 'maybe a string';
notSure = false;

// unknown:安全的 any(必须先检查类型才能使用)
let uncertain: unknown = 4;
// uncertain.toFixed();  // 报错:unknown 类型不能直接使用

if (typeof uncertain === 'number') {
    console.log(uncertain.toFixed(2)); // 类型检查后可以使用
}

// void:无返回值(常用于函数)
function logMessage(msg: string): void {
    console.log(msg);
}

// never:永不存在的值的类型
function throwError(message: string): never {
    throw new Error(message);
}

function infiniteLoop(): never {
    while (true) {}
}

三、接口与类型别名

3.1 接口(Interface)

接口用于定义对象的形状,是 TypeScript 中最常用的类型定义方式:

// 基本接口
interface User {
    name: string;
    age: number;
    email?: string;        // 可选属性
    readonly id: number;   // 只读属性
}

const user: User = {
    id: 1,
    name: '平平',
    age: 25,
    // email 可以不提供
};
// user.id = 2;  // 报错:只读属性不能修改

// 接口继承
interface Animal {
    name: string;
}

interface Dog extends Animal {
    breed: string;
    bark(): void;
}

const dog: Dog = {
    name: '旺财',
    breed: '金毛',
    bark() {
        console.log('Woof!');
    }
};

// 函数类型接口
interface SearchFunc {
    (source: string, subString: string): boolean;
}

const mySearch: SearchFunc = function(src, sub) {
    return src.includes(sub);
};

// 可索引类型
interface StringArray {
    [index: number]: string;
}
const arr: StringArray = ['a', 'b', 'c'];

3.2 类型别名(Type Alias)

// 基本类型别名
type Name = string;
type Age = number;

// 对象类型别名
type User = {
    name: string;
    age: number;
    email?: string;
    readonly id: number;
};

// 联合类型别名
type ID = string | number;
type Status = 'pending' | 'success' | 'failed';

// 函数类型别名
type Callback = (data: any) => void;

// 使用类型别名
const userId: ID = 12345;
const userName: Name = '平平';
const handleResponse: Callback = (data) => console.log(data);

3.3 interface 与 type 的区别

// interface 声明合并
interface Window {
    customProp: string;
}
interface Window {
    anotherProp: number;
}
// 最终 Window 类型包含 customProp 和 anotherProp

// type 交叉类型扩展
type Animal = {
    name: string;
};
type Dog = Animal & {
    breed: string;
};

四、函数类型

4.1 函数声明

// 为参数和返回值添加类型
function add(x: number, y: number): number {
    return x + y;
}

// 可选参数(用 ? 标记,必须在必选参数之后)
function greet(name: string, greeting?: string): string {
    return `${greeting || 'Hello'}, ${name}!`;
}
greet('平平');              // 'Hello, 平平!'
greet('平平', 'Hi');        // 'Hi, 平平!'

// 默认参数
function greetWithDefault(name: string, greeting: string = 'Hello'): string {
    return `${greeting}, ${name}!`;
}

// 剩余参数
function sum(...numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15

4.2 函数重载

// 重载签名
function reverse(x: number): number;
function reverse(x: string): string;
// 实现签名
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else {
        return x.split('').reverse().join('');
    }
}

const num: number = reverse(12345);   // 54321
const str: string = reverse('hello'); // 'olleh'

五、泛型

泛型(Generics)允许我们编写可复用的、类型安全的代码。它像是类型的"参数",让我们可以在使用时再指定具体类型。

5.1 泛型函数

// 泛型函数:T 是类型参数
function identity<T>(arg: T): T {
    return arg;
}

// 显式指定类型
const result1: string = identity<string>('hello');
const result2: number = identity<number>(42);

// 类型推断(推荐,让 TS 自动推断)
const result3 = identity('world');  // 推断为 string
const result4 = identity(true);     // 推断为 boolean

// 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}
const p = pair('hello', 42); // [string, number]

// 泛型约束:限制类型必须有某些属性
interface Lengthwise {
    length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
logLength('hello');   // 5
logLength([1, 2, 3]); // 3
// logLength(42);     // 报错:number 没有 length 属性

5.2 泛型接口与类

// 泛型接口
interface Box<T> {
    value: T;
}
const stringBox: Box<string> = { value: 'hello' };
const numberBox: Box<number> = { value: 42 };

// 泛型类
class Stack<T> {
    private items: T[] = [];

    push(item: T): void {
        this.items.push(item);
    }

    pop(): T | undefined {
        return this.items.pop();
    }

    peek(): T | undefined {
        return this.items[this.items.length - 1];
    }

    get size(): number {
        return this.items.length;
    }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2

const stringStack = new Stack<string>();
stringStack.push('a');
stringStack.push('b');

5.3 泛型工具类型

// Partial:所有属性变为可选
interface User {
    id: number;
    name: string;
    age: number;
}
type PartialUser = Partial<User>;
// 等价于 { id?: number; name?: string; age?: number; }

// Required:所有属性变为必选
type RequiredUser = Required<PartialUser>;

// Readonly:所有属性变为只读
type ReadonlyUser = Readonly<User>;

// Pick:挑选部分属性
type UserBasic = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }

// Omit:排除部分属性
type UserWithoutId = Omit<User, 'id'>;
// { name: string; age: number; }

// Record:键值对类型
type UserMap = Record<string, User>;

// ReturnType:获取函数返回值类型
function getUser() {
    return { name: '平平', age: 25 };
}
type UserType = ReturnType<typeof getUser>;

六、联合类型与交叉类型

6.1 联合类型

联合类型表示一个值可以是多种类型之一:

// 基本联合类型
let id: string | number;
id = 123;
id = 'abc';

// 字面量联合类型
type Direction = 'left' | 'right' | 'up' | 'down';
function move(direction: Direction): void {
    console.log(`向${direction}移动`);
}
move('left'); // OK
// move('diagonal'); // 报错

// 联合类型与 null/undefined
function greet(name: string | null) {
    if (name === null) {
        console.log('Hello, stranger!');
    } else {
        console.log(`Hello, ${name}!`);
    }
}

6.2 交叉类型

交叉类型将多个类型合并为一个类型:

interface BusinessPartner {
    name: string;
    credit: number;
}

interface Identity {
    id: number;
    name: string;
}

interface Contact {
    email: string;
    phone: string;
}

// 交叉类型:同时满足多个接口
type Employee = Identity & Contact;
type Customer = BusinessPartner & Contact;

const employee: Employee = {
    id: 1,
    name: '平平',
    email: 'ping@example.com',
    phone: '1234567890'
};

七、类型守卫

类型守卫(Type Guards)用于在条件块中缩小类型的范围:

7.1 typeof 类型守卫

function padLeft(value: string, padding: string | number): string {
    // typeof 缩小类型范围
    if (typeof padding === 'number') {
        return ' '.repeat(padding) + value; // padding 被识别为 number
    }
    return padding + value; // padding 被识别为 string
}

7.2 instanceof 类型守卫

class Cat {
    meow(): void { console.log('Meow!'); }
}

class Dog {
    bark(): void { console.log('Woof!'); }
}

function speak(pet: Cat | Dog): void {
    // instanceof 缩小类型范围
    if (pet instanceof Cat) {
        pet.meow(); // pet 被识别为 Cat
    } else {
        pet.bark(); // pet 被识别为 Dog
    }
}

7.3 in 类型守卫

interface Fish {
    swim(): void;
}

interface Bird {
    fly(): void;
}

function move(pet: Fish | Bird) {
    // in 操作符检查属性是否存在
    if ('swim' in pet) {
        pet.swim(); // pet 被识别为 Fish
    } else {
        pet.fly(); // pet 被识别为 Bird
    }
}

7.4 自定义类型守卫

interface ApiSuccess {
    success: true;
    data: string;
}

interface ApiError {
    success: false;
    error: string;
}

type ApiResponse = ApiSuccess | ApiError;

// 自定义类型谓词函数
function isSuccess(response: ApiResponse): response is ApiSuccess {
    return response.success === true;
}

function handleResponse(response: ApiResponse) {
    if (isSuccess(response)) {
        console.log(response.data); // response 被识别为 ApiSuccess
    } else {
        console.log(response.error); // response 被识别为 ApiError
    }
}

八、实战案例:用 TS 重构一个 API 接口

下面我们用 TypeScript 实现一个类型安全的 API 请求封装:

// types/api.ts — 定义类型

// API 响应基础结构
interface BaseResponse<T> {
    code: number;
    message: string;
    timestamp: number;
}

// 成功响应
interface SuccessResponse<T> extends BaseResponse<T> {
    code: 200;
    success: true;
    data: T;
}

// 错误响应
interface ErrorResponse extends BaseResponse<never> {
    code: 400 | 401 | 403 | 404 | 500;
    success: false;
    error: string;
}

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

// 用户相关类型
interface User {
    id: number;
    username: string;
    email: string;
    avatar: string;
    createdAt: string;
}

interface UserListParams {
    page?: number;
    pageSize?: number;
    keyword?: string;
}

interface PaginatedData<T> {
    list: T[];
    total: number;
    page: number;
    pageSize: number;
}

// 文章相关类型
interface Article {
    id: number;
    title: string;
    content: string;
    author: User;
    tags: string[];
    publishedAt: string;
}

type ArticleCreateData = Omit<Article, 'id' | 'author' | 'publishedAt'>;
type ArticleUpdateData = Partial<ArticleCreateData>;

// HTTP 方法类型
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

// 请求配置
interface RequestOptions {
    method?: HttpMethod;
    headers?: Record<string, string>;
    body?: unknown;
    params?: Record<string, string | number | boolean | undefined>;
}
// utils/http.ts — HTTP 请求封装

class HttpClient {
    private baseUrl: string;
    private defaultHeaders: Record<string, string>;

    constructor(baseUrl: string) {
        this.baseUrl = baseUrl;
        this.defaultHeaders = {
            'Content-Type': 'application/json'
        };
    }

    // 设置认证 token
    setAuthToken(token: string | null): void {
        if (token) {
            this.defaultHeaders['Authorization'] = `Bearer ${token}`;
        } else {
            delete this.defaultHeaders['Authorization'];
        }
    }

    // 构建完整 URL
    private buildUrl(path: string, params?: RequestOptions['params']): string {
        const url = new URL(`${this.baseUrl}${path}`);
        if (params) {
            Object.entries(params).forEach(([key, value]) => {
                if (value !== undefined) {
                    url.searchParams.append(key, String(value));
                }
            });
        }
        return url.toString();
    }

    // 通用请求方法
    private async request<T>(
        path: string,
        options: RequestOptions = {}
    ): Promise<ApiResponse<T>> {
        const { method = 'GET', headers = {}, body, params } = options;

        try {
            const response = await fetch(this.buildUrl(path, params), {
                method,
                headers: { ...this.defaultHeaders, ...headers },
                body: body ? JSON.stringify(body) : undefined
            });

            const data: ApiResponse<T> = await response.json();
            return data;
        } catch (error) {
            return {
                code: 500,
                success: false,
                message: '网络请求失败',
                error: error instanceof Error ? error.message : 'Unknown error',
                timestamp: Date.now()
            };
        }
    }

    // GET 请求
    get<T>(path: string, params?: RequestOptions['params']): Promise<ApiResponse<T>> {
        return this.request<T>(path, { method: 'GET', params });
    }

    // POST 请求
    post<T>(path: string, body?: unknown): Promise<ApiResponse<T>> {
        return this.request<T>(path, { method: 'POST', body });
    }

    // PUT 请求
    put<T>(path: string, body?: unknown): Promise<ApiResponse<T>> {
        return this.request<T>(path, { method: 'PUT', body });
    }

    // DELETE 请求
    delete<T>(path: string): Promise<ApiResponse<T>> {
        return this.request<T>(path, { method: 'DELETE' });
    }
}
// services/userService.ts — 用户服务

const http = new HttpClient('https://api.example.com');

// 类型守卫:检查是否为成功响应
function isSuccessResponse<T>(res: ApiResponse<T>): res is SuccessResponse<T> {
    return res.success === true;
}

export const userService = {
    // 获取用户列表
    async getUsers(params: UserListParams = {}): Promise<PaginatedData<User>> {
        const response = await http.get<PaginatedData<User>>('/users', {
            page: params.page ?? 1,
            pageSize: params.pageSize ?? 10,
            keyword: params.keyword
        });

        if (isSuccessResponse(response)) {
            return response.data;
        }
        throw new Error(response.error);
    },

    // 获取单个用户
    async getUserById(id: number): Promise<User> {
        const response = await http.get<User>(`/users/${id}`);
        if (isSuccessResponse(response)) {
            return response.data;
        }
        throw new Error(response.error);
    },

    // 创建用户
    async createUser(data: Omit<User, 'id' | 'createdAt'>): Promise<User> {
        const response = await http.post<User>('/users', data);
        if (isSuccessResponse(response)) {
            return response.data;
        }
        throw new Error(response.error);
    },

    // 更新用户
    async updateUser(id: number, data: Partial<User>): Promise<User> {
        const response = await http.put<User>(`/users/${id}`, data);
        if (isSuccessResponse(response)) {
            return response.data;
        }
        throw new Error(response.error);
    },

    // 删除用户
    async deleteUser(id: number): Promise<void> {
        const response = await http.delete<void>(`/users/${id}`);
        if (!isSuccessResponse(response)) {
            throw new Error(response.error);
        }
    }
};

export const articleService = {
    // 获取文章列表
    async getArticles(page: number = 1): Promise<PaginatedData<Article>> {
        const response = await http.get<PaginatedData<Article>>('/articles', { page });
        if (isSuccessResponse(response)) {
            return response.data;
        }
        throw new Error(response.error);
    },

    // 创建文章
    async createArticle(data: ArticleCreateData): Promise<Article> {
        const response = await http.post<Article>('/articles', data);
        if (isSuccessResponse(response)) {
            return response.data;
        }
        throw new Error(response.error);
    },

    // 更新文章
    async updateArticle(id: number, data: ArticleUpdateData): Promise<Article> {
        const response = await http.put<Article>(`/articles/${id}`, data);
        if (isSuccessResponse(response)) {
            return response.data;
        }
        throw new Error(response.error);
    }
};
// 使用示例
async function main() {
    try {
        // 获取用户列表
        const result = await userService.getUsers({
            page: 1,
            pageSize: 20,
            keyword: '平平'
        });
        console.log(`共 ${result.total} 个用户`);
        result.list.forEach(user => {
            console.log(`${user.id}: ${user.username} (${user.email})`);
        });

        // 创建文章
        const newArticle = await articleService.createArticle({
            title: 'TypeScript 入门',
            content: '这是一篇关于 TypeScript 的文章...',
            tags: ['TypeScript', '教程']
        });
        console.log(`文章创建成功,ID: ${newArticle.id}`);

        // 更新文章
        const updated = await articleService.updateArticle(newArticle.id, {
            title: 'TypeScript 进阶'
        });
        console.log(`文章已更新: ${updated.title}`);
    } catch (error) {
        console.error('操作失败:', error instanceof Error ? error.message : error);
    }
}

main();

九、总结

通过本文的学习,你应该掌握了 TypeScript 的核心概念:

TypeScript 的核心价值在于在编译阶段就发现潜在错误,让代码更加健壮、可维护。建议在实际项目中逐步应用,从简单的类型注解开始,逐渐深入到泛型、高级类型等概念。配合 IDE 的智能提示,TypeScript 能大幅提升开发效率和代码质量。

动手挑战

学到这里,不妨动手试一试以下练习,巩固你的理解:

  1. 基础练习:回顾本文核心概念,用自己的话总结关键知识点。
  2. 进阶实践:将文中的示例代码运行一遍,尝试修改参数观察变化。
  3. 拓展思考:想一想这个技术/方法还能应用在哪些场景中?

小贴士:遇到问题时,先独立思考,再查阅资料,最后请教他人——这是成长最快的学习方式。

赞赏支持

本文更新于 2026-08-22,环境 Python 3.12