TypeScript 教程 - JavaScript 的超集

项目概述

TypeScript 是 JavaScript 的超集,它添加了静态类型检查和其他特性,提高了代码的可维护性和可扩展性。TypeScript 代码最终会被编译为 JavaScript 代码,使其在任何 JavaScript 运行环境中运行。

核心特点

  1. 类型系统:添加静态类型检查,提高代码质量

  2. 接口:定义对象的结构和类型

  3. 泛型:支持泛型编程,提高代码复用性

  4. 命名空间:提供命名空间,避免命名冲突

  5. 模块:支持模块化编程,提高代码组织性

  6. 装饰器:支持装饰器语法,用于元编程

  7. 类型推断:自动推断变量类型,减少类型注解

  8. 类型别名:支持类型别名,提高代码可读性

  9. 联合类型:支持联合类型,增加类型灵活性

  10. 交叉类型:支持交叉类型,组合多个类型

安装设置

1. 局部安装(推荐)

在项目中局部安装 TypeScript:

# 使用 npm
npm install --save-dev typescript

# 使用 yarn
yarn add --dev typescript

# 使用 pnpm
pnpm add --save-dev typescript

2. 全局安装

在系统中全局安装 TypeScript:

# 使用 npm
npm install -g typescript

# 使用 yarn
yarn global add typescript

# 使用 pnpm
pnpm add -g typescript

3. 初始化配置

初始化 TypeScript 配置:

# 在项目根目录执行
npx tsc --init

# 或使用全局安装的 TypeScript
tsc --init

这会生成 tsconfig.json 文件,包含 TypeScript 的配置选项。

4. 基本配置

修改 tsconfig.json 文件,配置 TypeScript:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2015", "dom"],
    "allowJs": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

基本使用

1. 编译 TypeScript 文件

编译单个 TypeScript 文件或目录:

# 编译单个文件
npx tsc src/file.ts

# 编译目录
npx tsc

# 监视文件变化并自动编译
npx tsc --watch

2. 配置脚本

package.json 中配置脚本:

{
  "scripts": {
    "build": "tsc",
    "build:watch": "tsc --watch",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts"
  }
}

然后可以通过以下命令执行:

# 编译代码
npm run build

# 监视模式编译
npm run build:watch

# 运行编译后的代码
npm start

# 使用 ts-node 直接运行 TypeScript 代码
npm run dev

3. 基本类型

TypeScript 支持以下基本类型:

// 布尔类型
let isDone: boolean = false;

// 数字类型
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

// 字符串类型
let color: string = "blue";
let fullName: string = `Bob Bobbington`;

// 数组类型
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

// 元组类型
let x: [string, number];
x = ["hello", 10];

// 枚举类型
enum Color {
  Red,
  Green,
  Blue
}
let c: Color = Color.Green;

// 任意类型
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;

// 空类型
let unusable: void = undefined;

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

// never 类型
function error(message: string): never {
  throw new Error(message);
}

// object 类型
let obj: object = {a: 1, b: 2};

高级功能

1. 接口

接口定义了对象的结构和类型:

// 基本接口
interface Person {
  name: string;
  age: number;
}

let person: Person = {
  name: "John",
  age: 30
};

// 可选属性
interface Person {
  name: string;
  age?: number;
}

// 只读属性
interface Person {
  readonly id: number;
  name: string;
}

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

let mySearch: SearchFunc = function(source: string, subString: string): boolean {
  return source.includes(subString);
};

// 可索引接口
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray = ["Bob", "Fred"];

// 类接口
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) {}
}

// 继承接口
interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square: Square = {
  color: "blue",
  sideLength: 10
};

2. 泛型

泛型支持泛型编程,提高代码复用性:

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

let output1 = identity<string>("myString");
let output2 = identity<number>(100);

// 泛型接口
interface GenericIdentityFn<T> {
  (arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

// 泛型类
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

// 泛型约束
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

loggingIdentity("hello");
loggingIdentity([1, 2, 3]);

// 泛型参数默认类型
function createArray<T = string>(length: number, value: T): T[] {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

let strings = createArray(3, "hello");
let numbers = createArray<number>(3, 100);

3. 命名空间

命名空间提供命名空间,避免命名冲突:

// 基本命名空间
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = /^[0-9]+$/;

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s);
    }
  }
}

// 使用命名空间
let strings = ["Hello", "98052", "101"];
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// 嵌套命名空间
namespace Validation {
  export namespace Letters {
    export class LettersOnlyValidator implements StringValidator {
      isAcceptable(s: string) {
        return lettersRegexp.test(s);
      }
    }
  }
}

// 别名
import val = Validation.Letters.LettersOnlyValidator;
let validator = new val();

4. 模块

模块支持模块化编程,提高代码组织性:

// 导出模块
// validation.ts
export interface StringValidator {
  isAcceptable(s: string): boolean;
}

export const lettersRegexp = /^[A-Za-z]+$/;
export const numberRegexp = /^[0-9]+$/;

export class LettersOnlyValidator implements StringValidator {
  isAcceptable(s: string) {
    return lettersRegexp.test(s);
  }
}

export class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}

// 导入模块
// app.ts
import { StringValidator, LettersOnlyValidator, ZipCodeValidator } from "./validation";

let strings = ["Hello", "98052", "101"];
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();

// 导入整个模块
import * as Validation from "./validation";

let validator = new Validation.LettersOnlyValidator();

// 导出默认
// my-module.ts
export default function (x: number): number {
  return x + 1;
}

// 导入默认
import add from "./my-module";
add(1); // 2

// 重新导出
export { LettersOnlyValidator as RegExpBasedValidator } from "./validation";

// 导出 * 从
import * as validation from "./validation";
export { validation };

5. 装饰器

装饰器支持装饰器语法,用于元编程:

// 类装饰器
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

// 方法装饰器
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;
  }
}

// 访问器装饰器
function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}

class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }

  @configurable(false)
  get x() { return this._x; }

  @configurable(false)
  get y() { return this._y; }
}

// 属性装饰器
function format(formatString: string) {
  return function (target: any, propertyKey: string) {
    // 实现装饰器逻辑
  };
}

class Greeter {
  @format("Hello, %s")
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

// 参数装饰器
function required(target: any, propertyKey: string, parameterIndex: number) {
  // 实现装饰器逻辑
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet(@required name: string) {
    return "Hello, " + name + ", " + this.greeting;
  }
}

实际应用场景

1. React 项目

场景:在 React 项目中使用 TypeScript 提高代码质量

实现步骤

  1. 安装必要的依赖:
npm install --save-dev typescript @types/react @types/react-dom
  1. 配置 TypeScript:
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}
  1. 编写 TypeScript React 组件:
import React from 'react';

interface Props {
  name: string;
  age?: number;
  onSubmit: (data: { name: string; age: number }) => void;
}

const UserForm: React.FC<Props> = ({ name, age = 18, onSubmit }) => {
  const [formData, setFormData] = React.useState({ name, age });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value, type } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'number' ? parseInt(value) : value
    }));
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    onSubmit(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
      </div>
      <div>
        <label>Age:</label>
        <input
          type="number"
          name="age"
          value={formData.age}
          onChange={handleChange}
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

export default UserForm;

2. Node.js 项目

场景:在 Node.js 项目中使用 TypeScript 提高代码质量

实现步骤

  1. 安装必要的依赖:
npm install --save-dev typescript @types/node
npm install --save-dev ts-node
  1. 配置 TypeScript:
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
  1. 编写 TypeScript Node.js 代码:
// src/index.ts
import express from 'express';
import { Request, Response } from 'express';

const app = express();
const port = 3000;

app.use(express.json());

interface User {
  id: number;
  name: string;
  email: string;
}

let users: User[] = [
  { id: 1, name: 'John Doe', email: 'john@example.com' },
  { id: 2, name: 'Jane Doe', email: 'jane@example.com' }
];

app.get('/users', (req: Request, res: Response) => {
  res.json(users);
});

app.get('/users/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const user = users.find(user => user.id === id);
  if (user) {
    res.json(user);
  } else {
    res.status(404).json({ message: 'User not found' });
  }
});

app.post('/users', (req: Request, res: Response) => {
  const newUser: User = {
    id: users.length + 1,
    name: req.body.name,
    email: req.body.email
  };
  users.push(newUser);
  res.status(201).json(newUser);
});

app.put('/users/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(user => user.id === id);
  if (userIndex !== -1) {
    users[userIndex] = {
      ...users[userIndex],
      name: req.body.name,
      email: req.body.email
    };
    res.json(users[userIndex]);
  } else {
    res.status(404).json({ message: 'User not found' });
  }
});

app.delete('/users/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(user => user.id === id);
  if (userIndex !== -1) {
    users.splice(userIndex, 1);
    res.json({ message: 'User deleted' });
  } else {
    res.status(404).json({ message: 'User not found' });
  }
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

3. 工具库项目

场景:创建一个 TypeScript 工具库,提高代码复用性

实现步骤

  1. 安装必要的依赖:
npm install --save-dev typescript
  1. 配置 TypeScript:
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "declaration": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
  1. 编写 TypeScript 工具库:
// src/utils.ts
export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout;
  return (...args: Parameters<T>) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), wait);
  };
}

export function throttle<T extends (...args: any[]) => any>(
  func: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle: boolean;
  return (...args: Parameters<T>) => {
    if (!inThrottle) {
      func(...args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

export function deepClone<T>(obj: T): T {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  if (obj instanceof Date) {
    return new Date(obj.getTime()) as unknown as T;
  }
  if (obj instanceof Array) {
    return obj.map(item => deepClone(item)) as unknown as T;
  }
  if (typeof obj === 'object') {
    const clonedObj = {} as T;
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        clonedObj[key] = deepClone(obj[key]);
      }
    }
    return clonedObj;
  }
  return obj;
}

export function formatDate(date: Date, format: string): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  const seconds = String(date.getSeconds()).padStart(2, '0');

  return format
    .replace('YYYY', year.toString())
    .replace('MM', month)
    .replace('DD', day)
    .replace('HH', hours)
    .replace('mm', minutes)
    .replace('ss', seconds);
}

最佳实践

  1. 启用严格模式:启用 strict: true,获得最大的类型安全性

  2. 使用类型推断:利用 TypeScript 的类型推断,减少类型注解

  3. 定义明确的接口:为复杂对象定义明确的接口,提高代码可读性

  4. 使用泛型:使用泛型提高代码复用性

  5. 合理使用 any:尽量避免使用 any 类型,保持类型安全性

  6. 使用联合类型:使用联合类型增加类型灵活性

  7. 使用类型别名:使用类型别名提高代码可读性

  8. 模块化组织:使用模块和命名空间组织代码,提高代码可维护性

  9. 使用装饰器:合理使用装饰器,简化代码

  10. 编写类型声明文件:为第三方库编写类型声明文件,提高类型安全性

常见问题与解决方案

1. 类型错误

问题:TypeScript 类型错误,导致编译失败

解决方案

  • 检查类型注解是否正确
  • 使用类型断言解决类型不匹配问题
  • 调整类型定义,使其更准确
  • 使用联合类型处理多种可能的类型

2. 模块解析问题

问题:模块解析失败,找不到模块

解决方案

  • 检查模块路径是否正确
  • 配置 baseUrlpaths 选项
  • 确保安装了必要的类型声明文件
  • 检查 moduleResolution 配置

3. 编译速度问题

问题:TypeScript 编译速度慢

解决方案

  • 合理配置 includeexclude 选项
  • 使用 tsconfig.jsonreferences 功能拆分配置
  • 考虑使用 esbuildswc 等更快的编译器
  • 启用 incremental 编译选项

4. 与 JavaScript 集成问题

问题:与现有 JavaScript 代码集成困难

解决方案

  • 使用 allowJs: true 允许混合使用 JavaScript 和 TypeScript
  • 为 JavaScript 代码编写类型声明文件
  • 逐步迁移 JavaScript 代码到 TypeScript

5. 类型声明文件问题

问题:缺少第三方库的类型声明文件

解决方案

  • 安装 @types/xxx 类型声明文件
  • 为第三方库编写自定义类型声明文件
  • 使用 declare module 声明模块

参考资源

总结

TypeScript 是一个功能强大的 JavaScript 超集,它添加了静态类型检查和其他特性,提高了代码的可维护性和可扩展性。通过本文的介绍,你应该已经了解了 TypeScript 的核心功能、使用方法和最佳实践。

TypeScript 的优势在于:

  1. 类型安全:静态类型检查,减少运行时错误

  2. 代码可维护性:明确的类型定义,提高代码可读性

  3. 代码可扩展性:接口、泛型等特性,提高代码复用性

  4. 工具支持:丰富的 IDE 支持,提供代码提示和重构功能

  5. 生态系统:与 JavaScript 生态系统完全兼容,支持所有 JavaScript 库

TypeScript 适合各种项目,尤其是大型应用和团队协作项目。它不仅可以提高代码质量,还可以减少开发过程中的错误,提高开发效率。

如果你还没有在项目中使用 TypeScript,建议你尽快尝试,相信它会给你的开发工作带来很大的帮助。

« 上一篇 Babel 教程 - JavaScript 编译器 下一篇 » Framer Motion 教程 - React 的生产级动画库