TypeScript 详细教程

705次阅读
没有评论

共计 13295 个字符,预计需要花费 34 分钟才能阅读完成。

目录

  1. TypeScript 简介
  2. 环境搭建
  3. 基础类型
  4. 接口
  5. 类
  6. 函数
  7. 泛型
  8. 模块系统
  9. 类型进阶
  10. 配置与工程化

TypeScript 简介

TypeScript 是 JavaScript 的超集,添加了静态类型系统。它由微软开发,可以在任何支持 JavaScript 的地方运行。

主要特性

  • 静态类型检查 :在编译时发现错误
  • 类型推断 :自动推断变量类型
  • 接口和泛型 :更好的代码组织和复用
  • ES6+ 特性支持 :支持最新的 JavaScript 特性
  • 工具支持 :更好的 IDE 支持和代码补全

环境搭建

安装 TypeScript

# 全局安装
npm install -g typescript

# 或者作为项目依赖安装
npm install --save-dev typescript

验证安装

tsc --version

创建第一个 TypeScript 文件

创建 hello.ts:

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

const message = greet("TypeScript");
console.log(message);

编译 TypeScript

# 编译单个文件
tsc hello.ts

# 使用配置文件编译
tsc

tsconfig.json 配置

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

基础类型

基本类型

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

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

// 字符串
let color: string = "blue";
color = 'red';

// 模板字符串
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;

// 数组
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // 泛型语法

// 元组
let tuple: [string, number];
tuple = ["hello", 10]; // OK
// tuple = [10, "hello"]; // Error

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

enum ColorWithValues {
  Red = 1,
  Green = 2,
  Blue = 4
}

// Any
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;

// Void
function warnUser(): void {console.log("This is my warning message");
}

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

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

// Object
declare function create(o: object | null): void;
create({prop: 0}); // OK
create(null); // OK
// create(42); // Error

// 类型断言
let someValue: any = "this is a string";
let strLength1: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;

联合类型和类型别名

// 联合类型
let union: string | number;
union = "hello";
union = 42;
// union = true; // Error

// 类型别名
type StringOrNumber = string | number;
let value: StringOrNumber;
value = "hello";
value = 123;

// 字面量类型
type Direction = "left" | "right" | "up" | "down";
let move: Direction = "left";
// move = "diagonal"; // Error

// 交叉类型
interface BusinessPartner {
  name: string;
  credit: number;
}

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

type Employee = Identity & BusinessPartner;

let employee: Employee = {
  id: 1,
  name: "John",
  credit: 1000
};

接口

基础接口

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

function printPerson(person: Person): void {console.log(`ID: ${person.id}, Name: ${person.name}`);
  if (person.age) {console.log(`Age: ${person.age}`);
  }
}

let john: Person = {
  id: 1,
  name: "John"
};

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

let mySearch: SearchFunc = function(src: string, sub: string): boolean {return src.search(sub) > -1;
};

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

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

接口继承和实现

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

interface Square extends Shape {sideLength: number;}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;

// 多继承
interface PenStroke {penWidth: number;}

interface SquareWithPen extends Shape, PenStroke {sideLength: number;}

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

类

基础类

class Animal {
  // 属性
  private name: string; // 私有属性,只能在类内部访问
  protected age: number; // 受保护属性,可以在子类中访问
  public readonly species: string; // 公开只读属性

  // 静态属性
  static numberOfAnimals: number = 0;

  // 构造函数
  constructor(name: string, age: number, species: string) {
    this.name = name;
    this.age = age;
    this.species = species;
    Animal.numberOfAnimals++;
  }

  // 方法
  public move(distanceInMeters: number = 0): void {console.log(`${this.name} moved ${distanceInMeters}m.`);
  }

  // 获取私有属性
  public getName(): string {return this.name;}

  // 静态方法
  static getNumberOfAnimals(): number {return Animal.numberOfAnimals;}
}

继承和多态

// 继承
class Dog extends Animal {
  private breed: string;

  constructor(name: string, age: number, breed: string) {super(name, age, "Dog"); // 调用父类构造函数
    this.breed = breed;
  }

  // 方法重写
  public move(distanceInMeters: number = 5): void {console.log("Running...");
    super.move(distanceInMeters); // 调用父类方法
  }

  // 新方法
  public bark(): void {console.log("Woof! Woof!");
  }

  public getBreed(): string {return this.breed;}
}

// 使用
const dog = new Dog("Buddy", 3, "Golden Retriever");
dog.move(10);
dog.bark();
console.log(Animal.getNumberOfAnimals());

抽象类

abstract class Department {constructor(public name: string) {}

  // 抽象方法,必须在子类中实现
  abstract printMeeting(): void;

  // 具体方法
  printName(): void {console.log("Department name:" + this.name);
  }
}

class AccountingDepartment extends Department {constructor() {super("Accounting and Auditing");
  }

  printMeeting(): void {console.log("The Accounting Department meets each Monday at 10am.");
  }

  generateReports(): void {console.log("Generating accounting reports...");
  }
}

// 使用
let department: Department; // 允许创建对抽象类型的引用
// department = new Department(); // 错误:不能创建抽象类的实例
department = new AccountingDepartment(); // 允许对抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
// department.generateReports(); // 错误:方法在声明的抽象类中不存在 

访问器

class Employee {
  private _fullName: string = "";

  get fullName(): string {return this._fullName;}

  set fullName(newName: string) {if (newName && newName.length > 3) {this._fullName = newName;} else {console.log("Error: Name must be longer than 3 characters");
    }
  }
}

let employee = new Employee();
employee.fullName = "John Doe";
console.log(employee.fullName);

函数

函数类型

// 函数声明
function add(x: number, y: number): number {return x + y;}

// 函数表达式
let myAdd = function(x: number, y: number): number {return x + y;};

// 箭头函数
let myAddArrow = (x: number, y: number): number => x + y;

// 可选参数和默认参数
function buildName(firstName: string, lastName?: string, middleName: string = ""): string {if (lastName) {return `${firstName} ${middleName} ${lastName}`;
  }
  return firstName;
}

// 剩余参数
function buildNameRest(firstName: string, ...restOfName: string[]): string {return firstName + "" + restOfName.join(" ");
}

// 函数重载
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {if (d !== undefined && y !== undefined) {return new Date(y, mOrTimestamp, d);
  } else {return new Date(mOrTimestamp);
  }
}

const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 2020);
// const d3 = makeDate(1, 3); // Error: 没有需要 2 参数的重载 

this 和上下文

interface Card {
  suit: string;
  card: number;
}

interface Deck {suits: string[];
  cards: number[];
  createCardPicker(this: Deck): () => Card;}

let deck: Deck = {suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function(this: Deck) {return () => {let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);

      return {suit: this.suits[pickedSuit], card: pickedCard % 13};
    }
  }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card:" + pickedCard.card + "of" + pickedCard.suit);

泛型

基础泛型

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

let output1 = identity<string>("myString");
let output2 = identity("myString"); // 类型推断

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

let myIdentity: GenericIdentityFn<number> = identity;

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

  constructor(zeroValue: T, add: (x: T, y: T) => T) {
    this.zeroValue = zeroValue;
    this.add = add;
  }
}

let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);

泛型约束

interface Lengthwise {length: number;}

function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length); // 现在我们知道 arg 有 length 属性
  return arg;
}

// loggingIdentity(3); // Error: number 没有 length 属性
loggingIdentity({length: 10, value: 3}); // OK

// 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {return obj[key];
}

let x = {a: 1, b: 2, c: 3, d: 4};
getProperty(x, "a"); // OK
// getProperty(x, "m"); // Error: 参数类型 '"m"' 不能赋值给参数类型 '"a" | "b" | "c" | "d"'

泛型工具类型

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

// Partial - 所有属性变为可选
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {return { ...todo, ...fieldsToUpdate};
}

// Readonly - 所有属性变为只读
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
  description: "...",
  completed: false
};

// todo.title = "Hello"; // Error: 无法分配到 "title",因为它是只读属性

// Pick - 从类型中选择一组属性
type TodoPreview = Pick<Todo, "title" | "completed">;
const preview: TodoPreview = {
  title: "Clean room",
  completed: false
};

// Omit - 从类型中排除一组属性
type TodoInfo = Omit<Todo, "completed">;
const info: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm"
};

模块系统

导出和导入

// math.ts - 导出
export const PI = 3.14;

export interface Circle {radius: number;}

export function calculateCircumference(diameter: number): number {return diameter * PI;}

// 默认导出
export default class Calculator {static add(x: number, y: number): number {return x + y;}
}

// app.ts - 导入
import Calculator, {PI, Circle, calculateCircumference} from './math';

const circle: Circle = {radius: 10};
console.log(calculateCircumference(circle.radius * 2));
console.log(Calculator.add(5, 3));

// 重新导出
export {PI as MathPI} from './math';

命名空间

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 validators: {[s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

类型进阶

条件类型

type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"

// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]

// 排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type T5 = NonNullable<string | number | undefined>; // string | number

映射类型

interface Person {
  name: string;
  age: number;
  location: string;
}

// 使所有属性变为只读
type ReadonlyPerson = Readonly<Person>;

// 使所有属性变为可选
type PartialPerson = Partial<Person>;

// 选择特定属性
type PersonKeys = keyof Person; // "name" | "age" | "location"

// 自定义映射类型
type Optional<T> = {[P in keyof T]?: T[P];
};

type Nullable<T> = {[P in keyof T]: T[P] | null;
};

// 移除只读修饰符
type CreateMutable<T> = {-readonly [P in keyof T]: T[P];
};

// 移除可选修饰符
type Concrete<T> = {[P in keyof T]-?: T[P];
};

模板字面量类型

type World = "world";
type Greeting = `hello ${World}`; // "hello world"

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

// 字符串操作类型
type Getter<T extends string> = `get${Capitalize<T>}`;
type Setter<T extends string> = `set${Capitalize<T>}`;

type Name = "name";
type NameAccessors = Getter<Name> | Setter<Name>; // "getName" | "setName"

配置与工程化

完整 tsconfig.json

{
  "compilerOptions": {
    /* 基本选项 */
    "target": "ES2020", // 编译目标
    "module": "CommonJS", // 模块系统
    "lib": ["ES2020", "DOM"], // 包含的库文件
    "outDir": "./dist", // 输出目录
    "rootDir": "./src", // 源文件目录

    /* 严格类型检查选项 */
    "strict": true, // 启用所有严格类型检查选项
    "noImplicitAny": true, // 禁止隐式 any 类型
    "strictNullChecks": true, // 严格的 null 检查
    "strictFunctionTypes": true, // 严格的函数类型检查
    "strictBindCallApply": true, // 严格的 bind/call/apply 检查
    "strictPropertyInitialization": true, // 严格的属性初始化检查
    "noImplicitThis": true, // 禁止隐式的 this 类型
    "alwaysStrict": true, // 以严格模式解析并为每个源文件生成 "use strict"

    /* 额外检查 */
    "noUnusedLocals": true, // 报告未使用的局部变量的错误
    "noUnusedParameters": true, // 报告未使用的参数的错误
    "noImplicitReturns": true, // 不是函数中的所有代码路径都返回值时报错
    "noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误

    /* 模块解析选项 */
    "moduleResolution": "node", // 模块解析策略
    "baseUrl": "./", // 解析非相对模块名的基准目录
    "paths": { // 模块名到基于 baseUrl 的路径映射
      "@/*": ["src/*"],
      "@/components/*": ["src/components/*"]
    },
    "esModuleInterop": true, // 为导入 CommonJS 模块提供兼容性
    "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入

    /* Source Map 选项 */
    "sourceMap": true, // 生成相应的 .map 文件
    "declaration": true, // 生成相应的 .d.ts 文件
    "declarationMap": true, // 为声明文件生成 sourcemap

    /* 实验性选项 */
    "experimentalDecorators": true, // 启用装饰器
    "emitDecoratorMetadata": true, // 为装饰器提供元数据支持

    /* 高级选项 */
    "skipLibCheck": true, // 跳过所有声明文件的类型检查
    "forceConsistentCasingInFileNames": true // 禁止对同一文件使用不一致大小写的引用
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts"
  ]
}

与 React 集成

// React 组件示例
import React, {useState, useEffect} from 'react';

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

interface UserListProps {users: User[];
  onUserSelect: (user: User) => void;
}

const UserList: React.FC<UserListProps> = ({users, onUserSelect}) => {const [selectedUserId, setSelectedUserId] = useState<number | null>(null);

  const handleUserClick = (user: User) => {setSelectedUserId(user.id);
    onUserSelect(user);
  };

  return (
    <div>
      <h2>User List</h2>
      <ul>
        {users.map(user => (
          <li 
            key={user.id}
            onClick={() => handleUserClick(user)}
            style={{ 
              cursor: 'pointer',
              backgroundColor: selectedUserId === user.id ? '#f0f0f0' : 'transparent'
            }}
          >
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
};

// 自定义 Hook
function useUsers() {const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {const fetchUsers = async () => {
      try {setLoading(true);
        // 模拟 API 调用
        const response = await fetch('/api/users');
        const data = await response.json();
        setUsers(data);
      } catch (err) {setError(err.message);
      } finally {setLoading(false);
      }
    };

    fetchUsers();}, []);

  return {users, loading, error};
}

export default UserList;

最佳实践

  1. 启用严格模式 :始终在 tsconfig.json 中设置 "strict": true
  2. 使用明确的类型 :避免过度使用 any 类型
  3. 利用类型推断 :在明显的地方让 TypeScript 推断类型
  4. 使用接口定义对象形状 :而不是内联类型注解
  5. 利用泛型提高代码复用性
  6. 使用只读数组和元组 :确保数据不可变性
  7. 使用 const 断言 :用于字面量类型
// 好的实践
interface User {
  readonly id: number;
  name: string;
  email: string;
}

type UserList = ReadonlyArray<User>;

// const 断言
const positions = ["left", "right", "top", "bottom"] as const;
type Position = typeof positions[number]; // "left" | "right" | "top" | "bottom"

正文完
 0
一诺
版权声明:本站原创文章,由 一诺 于2025-10-15发表,共计13295字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码