TypeScript 总结

概述

TypeScript 是微软开放的开源编程语言,是 JavaScript 的超级。

特性:

  • 类型安全:编译时捕获错误,减少运行时 bug。
  • IDE 更好的支持:智能提示/自动补全/重构工具。
  • 代码可维护性:清晰的类型定义让代码更容易理解。
  • 最新 ES 特性:支持最新的 JavaScript 特性。

官方文档

中文文档

TypeScript资料

运行ts文件

无论是在浏览器环境下,还是在Node环境下,TypeScript程序都是不能直接运行的。

在这里插入图片描述

方式一:将TypeScript编译为JavaScript后运行

一、安装typescript:

npm install -g typescript

二、将ts文件编译为js文件:

tsc code.ts

三、运行js文件:

node code.js

查看tsc版本:

tsc -v

使用ES5编译ts代码:

tsc -t es5 demo.ts

方式二:直接运行TypeScript文件

一、安装ts-node:

npm install -g ts-node

二、执行 tsc --init 命令,生成 tsconfig.json 配置文件。

三、运行ts文件:

ts-node code.ts

四、或者通过 VSCode 中的 Code Runner 插件运行。

五、配置 tsconfig.json

创建 tsconfig.json:

tsc --init

tsconfig.json 说明:

{
  "compilerOptions": {
    "target": "ES2020",                  // 编译目标
    "module": "commonjs",                // 模块系统
    "lib": ["ES2020"],                   // 包含的库文件
    "outDir": "./dist",                  // 输出目录
    "rootDir": "./src",                  // 源代码目录
    "strict": true,                      // 严格模式
    "esModuleInterop": true,            // ES 模块互操作
    "skipLibCheck": true,               // 跳过库文件检查
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

TypeScript VS JavaScript

// JavaScript
function add(a, b) {
    return a + b;
}
const ret = add(1, "2")
console.log(ret) // 12
// TypeScript
function add(a: number, b: number): number {
    return a + b;
}
// const ret = add(1, "2") // 编译时报错
const ret = add(1, 2)
console.log(ret) // 3

类型

  • 使用 let 关键字声明变量
  • 使用 const 关键字声明常量

基本类型

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

// 数值类型
let binary: number = 0b1010 // 二进制
let octal: number = 0o123 // 八进制
let decimal: number = 6 // 十进制
let hex: number = 0x123 // 十六进制
let infinity: number = Infinity // 无穷大

// 字符串
let color: string = 'red';
let name: string = "小明"
let info: string = `hello ${name}`

// 数组
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]

// 元组
let x: [string, number]
x = ["hello", 1]
console.log(x[0]) // hello
console.log(x[1]) // 1

// null和 undefined
let a: null = null
let b: undefined = undefined

// any:任意类型,没有类型检查
let a: any = "hello"
a = 10
a = true
console.log(a) // true

// unknow:未知类型,有类型检查
let a: unknown = 1.5
// let ret = Math.round(a) // 提示错误
if (typeof a === 'number') {
    let ret = Math.round(a)
}

// void:没有返回值
function add(a: number, b: number): void {
    console.log(a + b);
}

// never:没有值
function error(message: string): never {
    throw new Error(message);
}

// object
let obj: object = {name: "小明", age: 18}
any、object和unknown区别
  • any:任意类型,完全绕过类型检查,安全性低。
  • object: 非原始类型(除string/number/boolean/symbol/null/undefined外),安全性中等。
  • unknown:未知类型,类型安全的any,推荐使用,需要通过类型检查或断言后使用。

类型断言

类型断言:指开发者手动告诉编译器某个值的具体类型的方式,也称为类型适配。

类型断言的2种方式:

  • 方式一:尖括号语法
  • 方式二:as 语法
// 方式一
let str: any = "hello world"
let len = (<string>str).length

// 方式二
let str: any = "hello world"
let len = (str as string).length

使用类型断言前:

function getLength(x: number | string): number {
  if (typeof x === "number") {
    return x.toString().length
  } else {
    return x.length
  }
}

console.log(getLength(10)) // 2
console.log(getLength("abc")) // 3

使用类型断言后:

function getLength(x: number | string): number {
  if ((<string>x).length) {
    return (x as string).length
  } else {
    return x.toString().length
  }
}

console.log(getLength(10)) // 2
console.log(getLength("abc")) // 3

接口

  • 声明接口用 interface 关键字
  • 实现接口用 implements 关键字
  • 接口继承用 extends 关键字

基本用法

interface IPerson {
  id: number
  name: string
  age: number
  sex: boolean
  sum(x: number, y: number): number
}

const person: IPerson = {
  id: 1,
  name: "小明",
  age: 18,
  sex: true,
  sum: function (x: number, y: number): number {
    return x + y
  },
}

console.log(person) // { id: 1, name: '小明', age: 18, sex: true, sum: [Function: sum] }

可选属性

interface IPerson {
  id: number
  name: string
  age?: number
  sex?: boolean
  sum?(x: number, y: number): number
}

const person: IPerson = {
  id: 1,
  name: "小明",
}

console.log(person) // { id: 1, name: '小明' }

只读属性

在属性名前加上 readonly 关键字表示只读属性,值一旦被定义则不能被修改。

interface IPerson {
  readonly id: number
  name: string
  age?: number
  sex?: boolean
  sum?(x: number, y: number): number
}

const person: IPerson = {
  id: 1,
  name: "小明",
}

// person.id = 2 // 报错,只读属性不能修改

函数类型接口

在 TypeScript 中可以利用接口定义函数类型。

interface SumFn {
  (x: number, y: number): number
}

const mySum: SumFn = (x, y) => x + y

console.log(mySum(1, 2)) // 3;

可索引类型

TypeScript 的可索引类型(Indexable Types)通过 索引签名 定义动态属性访问规则,是处理数组、字典等动态数据结构的核心工具。

语法格式:[keyType: KeyType]: ValueType

  • KeyType:索引键类型
  • ValueType:索引值类型
// 数字索引
interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = ["hello", "world"];
let a = myArray[0];

console.log(a); // hello
// 字符串索引
interface UserMap {
    [key: string]: { name: string; age: number };
}

const users: UserMap = {
    a: {name: "小白", age: 19},
    b: {name: "小黑", age: 30}
};

console.log(users["a"]);

接口继承

interface Shape {
    color: string;
}

interface Square extends Shape {
    slideLength: number;
}

let squares: Square = {
    color: "RED",
    slideLength: 5,
};

console.log(squares);

  • 声明类用 class 关键字
  • 创建对象用 new 关键字
  • 类继承用 extends 关键字

基本用法

class Person {
  // 属性:
  name: string
  age: number

  // 静态属性:
  static nationality: string = "China"

  // 构造函数:
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  // 方法:
  sayHello() {
    console.log(`Hello, my name is ${this.name}`)
  }

  // 静态方法:
  static sayNationality() {
    console.log(`我的国籍是${Person.nationality}`)
  }
}

let p = new Person("Tom", 18)
p.sayHello() // Hello, my name is Tom
Person.sayNationality() // 我的国籍是China

继承

class Animal {
  eat(food: string) {
    console.log(`${food}`)
  }
}

class Dog extends Animal {
  call() {
    console.log("汪汪汪")
  }
}

const dog = new Dog()
dog.eat("骨头") // 吃骨头
dog.call() // 汪汪汪

访问修饰符

  • public:公开的属性或方法。
  • private:私有属性或方法,只能在该类的内部访问。
  • protected:受保护的属性或方法,支持在子类中访问。
class Employee {
    public name: string; // 公开
    private salary: number; // 私有
    protected department: string; // 受保护

    constructor(name: string, salary: number, department: string) {
        this.name = name;
        this.salary = salary;
        this.department = department;
    }

    public getDetails(): string {
        return `${this.name} ${this.getSalary()} ${this.department}`;
    }

    private getSalary(): number {
        return this.salary;
    }
}

let xiaoming = new Employee("小明", 2000, "研发部门");
console.log(xiaoming.getDetails());

getter和setter

class Person {
    private _age: number = 0;
    get age() {
        return this._age;
    }

    set age(value: number) {
        if (value < 0) {
            throw new Error("Age must be greater than 0");
        }
        this._age = value;
    }
}

let person = new Person();
person.age = 5;
console.log(person.age);

抽象类

abstract class Animal {
    abstract eat(): void

    run() {
        console.log("奔跑");
    }
}

class Dog extends Animal {
    eat() {
        console.log("吃骨头");
    }
}

const dog = new Dog();
dog.eat(); // 吃骨头
dog.run(); // 奔跑

多态

class Animal {
    name: string
    constructor(name: string) {
        this.name = name
    }
    eat(food: string = "食物") {
        console.log(`${this.name}${food}`)
    }
}

class Dog extends Animal {
    constructor(name: string) {
        super(name)
    }
    eat(food: string = "骨头") {
        super.eat(food)
    }
}

class Cat extends Animal {
    constructor(name: string) {
        super(name)
    }
    eat(food: string = "鱼") {
        super.eat(food)
    }
}

function eat(animal: Animal) {
    animal.eat()
}

eat(new Dog("旺财")) // 旺财 吃 骨头
eat(new Cat("咪咪")) // 咪咪 吃 鱼

函数

TypeScript 是 JavaScript 的超集,JavaScript 的一些特性 TypeScript 中依然存在。

此外,TypeScript函数还增加了函数类型、可选参数、默认参数及剩余参数等。

JavaScript函数:

// 命名函数
function add1(x, y) {
    return x + y;
}

// 匿名函数
const add2 = function (x, y) {
    return x + y;
};

// 箭头函数
const add3 = (x, y) => x + y;

TypeScript函数:

// 命名函数
function add1(x: number, y: number): number {
    return x + y;
}

// 匿名函数
const add2 = function (x: number, y: number): number {
    return x + y;
};

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

函数类型

// 函数类型
const add4: (x: number, y: number) => number = (x: number, y: number) => x + y;

可选参数

function getName(firstName: string, lastName?: string): string {
    if (lastName) {
        return firstName + lastName;
    }
    return firstName;
}

console.log(getName("李"));
console.log(getName("李", "白"));

默认参数

function getName(firstName: string, lastName: string = "白"): string {
    if (lastName) {
        return firstName + lastName;
    }
    return firstName;
}

console.log(getName("李"));
console.log(getName("李", "黑"));

剩余参数

如果想同时操作多个参数,或者并不能明确知道会有多少个参数传递进来,就可以使用剩余参数。

function showSports(sport: string, ...others: string[]) {
    console.log(sport);
    console.log(others.join("-"));
}

showSports("足球", "篮球", "乒乓球");

// 足球
// 篮球-乒乓球

函数重载

function makeDate(timestamp: number): Date
function makeDate(year: number, month: number, day: number): Date
function makeDate(timestampOrYear: number, month?: number, day?: number): Date {
  if (month != null && day != null) {
    return new Date(timestampOrYear, month - 1, day)
  } else {
    return new Date(timestampOrYear)
  }
}

console.log(makeDate(1688368612562)) // 2023-11-01T00:36:52.562Z
console.log(makeDate(2008, 9, 10)) // 2008-09-09T16:00:00.000Z

泛型

泛型函数

function createArray<T>(length: number, value: T): T[] {
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}

const arr = createArray<string>(3, "x")
console.log(arr) // ['x', 'x', 'x']

泛型接口

interface IBaseCRUD<T> {
    add: (t: T) => void;
    getById: (id: number) => T | undefined;
    getAll: () => T[];
}

class User {
    id?: number; // id自增
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class UserCRUD implements IBaseCRUD<User> {

    private data: User[] = [];

    add(user: User): void {
        user.id = this.data.length + 1;
        this.data.push(user);
    }

    getById(id: number): User | undefined {
        return this.data.find((user) => user.id === id);
    }

    getAll(): User[] {
        return this.data;
    }
}

const userCRUD = new UserCRUD();
userCRUD.add(new User("a", 1));
userCRUD.add(new User("b", 2));
userCRUD.add(new User("c", 3));
console.log(userCRUD.getById(1));
// User { id: 1, name: 'a', age: 1 }
console.log(userCRUD.getAll());
// [
//     User { id: 1, name: 'a', age: 1 },
//     User { id: 2, name: 'b', age: 2 },
//     User { id: 3, name: 'c', age: 3 }
// ]

泛型类

abstract class GenericData<T> {
    abstract add(x: T, y: T): T
}

class NumberData extends GenericData<number> {
    add(x: number, y: number) {
        return x + y;
    }
}

class StringData extends GenericData<string> {
    add(x: string, y: string): string {
        return x + y;
    }
}

const numberData = new NumberData();
const result1 = numberData.add(11, 22);
console.log(result1); // 33

const stringData = new StringData();
const result2 = stringData.add("hello", "world");
console.log(result2); // helloworld

泛型约束

不使用接口:

function show<T extends Object>(x: T): void {
  console.log(x)
}

show<{ name: string; age: number }>({ name: "小明", age: 18 }) // { name: '小明', age: 18 }

使用接口:

function show<T extends Object>(x: T): void {
  console.log(x)
}

interface IUser {
  name: string
  age: number
}

show<IUser>({ name: "小明", age: 18 })

使用接口继承:

interface IUser {
  name: string
  age: number
}

function show<T extends IUser>(x: T): void {
  console.log(x)
}

show({ name: "小明", age: 18 })

枚举

基本用法

enum Color {
    RED,
    GREEN,
    BLUE
}
let c: Color = Color.RED;
console.log(c) // 0

自定义枚举

enum Sports {
    BASKETBALL = "篮球",
    FOOTBALL = "足球"
}
let s: Sports = Sports.BASKETBALL;
console.log(s) // 篮球
const enum Status {
    LOADING = 0,
    ERROR = 1,
    SUCCESS = 2,
}

let status = Status.SUCCESS;
console.log(status); // 2

高级类型

类型别名

// 基础类型
type Name = string
type Age = number
let name: Name = "小明";
let age: Age = 18;
console.log(`${name} ${age}`);
// 函数类型
type User = (name: Name, age: Age) => string
const user: User = (name: Name, age: Age) => {
    return `${name} ${age}`;
};
let ret = user("小白", 28);
console.log(ret); // 小白 28
// 对象类型
class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

type persons = Person[];

let persons: Person[] = [new Person("小白", 18), new Person("小黑", 28)];
console.log(persons);
// [ Person { name: '小白', age: 18 }, Person { name: '小黑', age: 28 } ]

交叉类型

交叉类型使用 &符号,将多个类型合并为一个新类型,新类型需同时满足所有原始类型的属性和方法。

语法格式:type NewType = TypeA & TypeB & …

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

interface Department {
    departmentId: number;
    departmentName: string;
}

type Employee = Person & Department;

const employee: Employee = {
    name: "小明",
    age: 18,
    departmentId: 100,
    departmentName: "研发部",
};
console.log(employee);
// { name: '小明', age: 18, departmentId: 100, departmentName: '研发部' }

联合类型

联合类型使用 |符号,表示变量可以是多种类型中的任意一种。

语法格式:type NewType = TypeA | TypeB | …

type NameAge = string | number;

function showNameAge(nameAge: NameAge) {
    if (typeof nameAge === "number") {
        console.log(`年龄:${nameAge}`);
    } else {
        console.log(`姓名:${nameAge}`);
    }
}

showNameAge("小明"); // 姓名:小明
showNameAge(18); // 年龄:18
// 限制输入
type Color = "RED" | "BLUE" | "GREEN"

function showColor(color: Color) {
    console.log(`颜色:${color}`);
}

showColor("RED");
// showColor("ABC"); // 报错

类型守卫

type Circle = {
  radius: number
}
type Rectangle = {
  width: number
  height: numbera
}
type Shape = Circle | Rectangle

function calculateArea(shape: Shape): number {
  if ("radius" in shape) {
    return Math.PI * shape.radius ** 2
  } else {
    return shape.width * shape.height
  }
}

let shape: Shape = { radius: 10 }
console.log(calculateArea(shape)) // 314.1592653589793
let shape2: Shape = { width: 10, height: 20 }
console.log(calculateArea(shape2))

类型守卫

作用:

  • 在运行时验证变量是否符合预期类型,避免因联合类型或 any类型导致的潜在错误。
  • 通过条件分支(如 if/elseswitch)将宽泛类型(如 string | number)逐步收窄为具体类型。
  • 确保在特定作用域内变量类型确定,允许安全访问属性或调用方法。

方式:

  • typeof类型守卫:适合检查基本类型
  • instanceof类型守卫:适合检查对象是否为某个类的实例
  • in操作符类型守卫:适合检查对象是否包含指定属性
  • 自定义类型守卫:适合复杂类型
  • 可辨识联合:通过公共属性区分

typeof类型守卫

function getNameOrAge(value: string | number) {
    if (typeof value === "string") {
        return `姓名:${value}`;
    } else if (typeof value === "number") {
        return `年龄:${value}`;
    } else {
        throw new Error("类型错误");
    }
}

instanceof类型守卫

class Bird {
    fly() {
        console.log("飞翔");
    }
}

class Fish {
    swim() {
        console.log("游泳");
    }
}

function move(animal: Bird | Fish) {
    if (animal instanceof Bird) {
        animal.fly();
    } else if (animal instanceof Fish) {
        animal.swim();
    }
}

in操作符类型守卫

interface Swimmer {
    swim(): void;
}

interface Runner {
    run(): void;
}

function move(animal: Swimmer | Runner) {
    if ("swim" in animal) {
        animal.swim();
    } else if ("run" in animal) {
        animal.run();
    }
}

自定义类型守卫

interface Cat {
    meow(): void;
}

interface Dog {
    bark(): void;
}

function isCat(pet: Cat | Dog): pet is Cat {
    return (pet as Cat).meow !== undefined;
}

function makeSound(pet: Cat | Dog) {
    if (isCat(pet)) {
        pet.meow();
    } else {
        pet.bark();
    }
} 

可辨识联合

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(shape: Shape): number {
    switch (shape.kind) {
        case "square":
            return shape.size * shape.size;
        case "rectangle":
            return shape.width * shape.height;
        case "circle":
            return Math.PI * shape.radius ** 2;
        default:
            throw new Error("类型错误");
    }
}

非空操作和可选链

interface User {
    name: string;
    address?: {
        city: string;
        street: string;
    };
}

const user1: User = {name: "小白"};
const user2: User = {name: "小黑", address: {city: "北京市"}};

可选链操作符

如果可选链左侧的值为null或undefined,则表达式返回undefined,不再执行右侧代码。

const city1 = user1.address?.city;
console.log(city1); // undefined
const city2 = user2.address?.city;
console.log(city2); // 北京市

类型断言操作符

类型断言表示告诉编译器该值存在,不进行空值检查,如果断言错误,运行时会抛出 TypeError。

let city = user2.address!.city;
console.log(city); // 北京市

空值合并操作符

如果左侧的值为null或undefined,则返回右侧的值。

let city = user1.address?.city ?? "unknow";
console.log(city); // unknow

模块化

TypeScript 支持多种模块系统,包含:ESM(ES模块)、CJS(CommonJS)等。

  • EMS:现在JavaScript标准,使用 import 和 export 语法。
  • CJS:Node.js默认模块系统,使用 require() 和 module.exports 语法。

导出

export function add(x: number, y: number): number {
    return x + y;
}

export function minus(x: number, y: number): number {
    return x - y;
}

export const PI = Math.PI;

export default class Calculator {
    add(x: number, y: number) {
        return x + y;
    }
}

导入

import Calculator, {add, minus, PI} from "./math";

console.log(add(11, 22));
console.log(minus(22, 11));
console.log(PI);
let calculator = new Calculator();
console.log(calculator.add(1, 2));

命名空间

命名空间是 TypeScript 早期的模块化解决方案,现在更推荐使用 ES 模块。但在一些场景下仍然有用:

命名空间使用 namespace 关键字定义。

namespace MathTool {
  export function add(a: number, b: number): number {
    return a + b
  }
  export function sub(a: number, b: number): number {
    return a - b
  }
  export function mul(a: number, b: number): number {
    return a * b
  }
  export function div(a: number, b: number): number {
    return a / b
  }
}

使用:

console.log(MathTool.add(1, 2)) // 3;
console.log(MathTool.sub(1, 2)) // -1;
console.log(MathTool.mul(1, 2)) // 2;
console.log(MathTool.div(1, 2)) // 0.5;

其他

interface和type区别

  • interface:主要用于定义对象结构,描述对象的形状。
  • type:定义类型别名,可以表示任何类型,包含:原始类型、联合类型、交叉类型等。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐