Typescript

形参展开

可以不用指定参数的个数,输入多个参数。

function multiply(n: number, ...m: number[]) {
  return m.map(x => n * x)
}

const a = multiply(10, 1, 2, 3, 4, 5)

console.log(a)

实参展开

const arr2 = [4, 5, 6]
arr1.push(...arr2)

arr1.push(7, 8, 9)

console.log(arr1)

参数解构

自动解构我们在方法中传入的参数,匹配对应的参数对象。

type ABC = { a: number, b: number, c: number };

function sum({a, b, c}: ABC) {
    console.log(a + b + c)
}

sum({a: 1, b: 2, c: 3});

对象类型

  • 匿名对象
  • 接口命名
  • 类型别名
/**
 * 匿名对象.
 */
function greet(person: {name: string, age: number}) {
    return 'hello ' + person.name + ' ' + person.age;
}

/**
 * 接口对象.
 */
interface Person{
    name: string;
    age: number;
}

/**
 * 类型别名.
 */
 type Person = {
     name: string;
     age: number;
 }

function greet(person: Person){
    return 'hello ' + person.name + ' ' + person.age;
}

const person = {
    name: 'John',
    age: 5,
};

console.log(greet(person))

可选属性

type Shape = {}

/**
 * 可选属性,通过添加 问号 可以把属性设置为可选状态,在传参时无需传递.
 */
interface PaintOptions {
    shape: Shape
    xPos?: number
    yPos?: number
}

/**
 * 直接通过接口类型来接收参数.
 */
// function painShape(opts: PaintOptions) {
//     let xPos = opts.xPos === undefined ? 0 : opts.xPos;
//     let yPos = opts.yPos === undefined ? 0 : opts.yPos;
//     console.log(xPos)
// }

/**
 * 通过解构来给可选属性设置默认值.
 */
// function paintShape({shape, xPos = 0, yPos = 0}: PaintOptions) {
//     console.log(xPos)
//     console.log(yPos)
// }

/**
 * 注意:下面的 Shape和number不是指参数类型,而是参数别名,相当于给参数定义了一个别名.
 */
function paintShape({shape: Shape, xPos: number = 0, yPos = 0}: PaintOptions) {
    console.log(number)
    console.log(Shape)
}


const shape: Shape = {}
paintShape({shape})

只读属性

/**
 * 总结.
 * readonly 只能控制对象本身不能被修改,但不能控制对象的属性被修改
 * readonly 也不能控制通过 被赋值的对象 来修改该对象
 */

interface Home {
    readonly resident: {
        name: string
        age: number
    }
}

/**
 * 如下:虽然 resident 对象被设置readonly,但其属性仍可被修改.
 */
function visitForBirthday(home: Home) {
    console.log(home.resident.name)
    home.resident.age++
    // 而resident本身不能被修改
    // home.resident = {};
}

interface Person {
    name: string
    age: number
}

interface ReadonlyPerson {
    readonly name: string
    readonly age: number
}

let writablePerson: Person = {
    name: 'Felix',
    age: 18
}

let readonlyPerson: ReadonlyPerson = writablePerson
// readonlyPerson自身无法被修改
// readonlyPerson.name = 'Tom'

// 可以通过修改 writablePerson 实现间接修改 readonlyPerson
writablePerson.name = 'John'
console.log(readonlyPerson)

索引签名

/**
 * 索引签名:我们可以给对象任意定义属性.
 */

/**
 * 定义索引签名:索引类型为number,值类型为string.
 */
interface StringArray {
    [index: number]: string
}

const myArray: StringArray = ['a', 'b', 'c'];
console.log(myArray[1])

/**
 * 定义索引签名:索引类型为字符串,值类型为number.
 */
interface TestString {
    [props: string]: number
}

let testString: TestString = {
    age: 100,
    a: 2,
    b: 3
    // 可定义任意多个属性,但属性值类型必须为number
}

/**
 * 定义联合类型,属性值类型可以为number或者string.
 */
interface UnionType {
    [union: string]: number | string
}

let unionTypes: UnionType = {
    a: 100,
    b: 'test'
}

扩展类型

/**
 * 通过extends实现扩展类型.
 */

interface Colorful {
    color: string;
}

interface Circle {
    radius: number;
}

interface ColorCircle extends Colorful, Circle {

}

const cc: ColorCircle = {
    color: 'red',
    radius: 100
}

console.log(cc)

交叉类型

/**
 * 交叉类型:通过 & 符号将多个类型的属性联合到一起,功能类似 extends 符号.
 */

interface Colorful {
    color: string;
}

interface Circle {
    radius: number;
}

type ColorfulCircle = Colorful & Circle

const colorCircle: ColorfulCircle = {
    color: 'red',
    radius: 100
}
console.log(colorCircle)

泛型对象类型

当不用泛型时,代码如下:需要对类型进行判断或者使用断言

interface Box {
    contents: unknown
}

let x: Box = {
    contents: 'hello world'
}

// 直接调用toLowerCase方法会报错,因为contents为 unknown 类型,不是string类型
// console.log(x.contents.toLowerCase())

// 可以通过类型判断或者断言的方式来将其作为string类型使用
if (typeof x.contents === 'string') {
    console.log(x.contents.toLowerCase())
}

console.log((x.contents as string).toLowerCase())

当使用泛型时:

interface Box<Type> {
    contents: Type
}

let x: Box<string> = {
    contents: 'Hello'
}
console.log(x.contents.toLowerCase())

泛型使用

function identity<Type>(args: Type): Type {
    return args
}
// 调用:<string> 可省略
identity<string>('hello')

将函数赋值给变量:

/**
 * 泛型函数.
 */
function identity<Type>(args: Type): Type {
    return args;
}

/**
 * 泛型接口.
 */
interface GenericIdentityFn {
    <Type>(args: Type): Type;
}

/**
 * 泛型变量:将一个函数赋值给一个变量,这样我们就可以重用该函数,也可以把函数作为参数进行传递,
 * 类似于其他数据类型,使代码更加模块化、可维护和可扩展.
 */
let myIdentity: GenericIdentityFn = identity

类型操纵

泛型类

通过泛型,你可以创建一个类,它可以在不同的使用场景下操作不同类型的数据,而不需要为每种数据类型编写单独的类。
泛型类是 TypeScript 强大的类型系统的一个重要组成部分,它为开发者提供了一种编写更安全、更灵活、更可重用代码的方式。

/**
 * 定义泛型类:它可以在不同的使用场景下操作不同类型的数据,而不需要为每种数据类型编写单独的类。.
 */
class GenericNumber<NumType> {
    zeroValue: NumType
    add: (x: NumType, y: NumType) => NumType
}

/**
 * 使用number类型作为泛型类的类型,并重写add方法.
 */
let myGeneric = new GenericNumber<number>()
myGeneric.zeroValue = 0
myGeneric.add = function (x, y) {
    return x + y
}

/**
 * 使用string类型作为泛型类的类型,并重写add方法..
 */
let myStringGeneric = new GenericNumber<string>()
myStringGeneric.zeroValue = 'hello'
myStringGeneric.add = function (x, y) {
    return x + y
}

泛型约束

在 TypeScript 中,泛型约束(Generic Constraints)是一种限制泛型参数只能扩展自特定的类型或实现特定的接口的方法。使用泛型约束可以确保泛型参数满足特定的要求,从而提供更严格的类型检查和更丰富的类型操作。

/**
 * 泛型约束:定义一个接口,其中包含length属性.
 */
interface Lengthwise {
    length: number
}

/**
 * 通过 extends Lengthwise 来保证传入的参数args必须具有length属性.
 */
function loggingIdentity<Type extends Lengthwise>(args: Type): Type {
    // 通过泛型约束 Lengthwise 来保证传入的参数args必须具有length属性
    args.length
    return args
}

loggingIdentity([1, 2])
// 当参数没有length属性时,即会报错
// loggingIdentity(3)

在泛型约束中使用类型函数

/**
 * Type:表示对象的类型。
 * Key:表示对象属性的键名,它被约束为 Type 类型的键之一,即 keyof Type。keyof Type 表示 Type 对象所有属性键的联合类型。
 *
 *  函数接受两个参数:
 *      obj:类型为 Type,即传入的对象。
 *      key:类型为 Key,即对象属性的键名,它必须是 obj 对象的一个有效键。
 */
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key]
}

let x = {
    a: 1,
    b: 2,
    c: 3
}
console.log(getProperty(x, 'b'));

在泛型中使用类类型

create 函数的泛型特性使其非常灵活,因为它不依赖于任何特定的类或构造函数,而是可以在运行时指定任何构造函数,只要这个构造函数符合传入的类型签名。这种模式在设计模式(如工厂模式)中非常有用,允许你编写与具体类解耦的代码,提高代码的可维护性和可扩展性。

/**
 * 定义了一个 TypeScript 中的泛型函数 create,其主要用途是创建对象实例。
 * 工厂模式.
 */
function create<Type>(c: new () => Type): Type {
    return new c()
}

class Animal {
    speak() {
        console.log('speak.');
    }
}

let myAnimal = create(Animal);

从类型中创建类型

keyof

keyof 是一个高级类型操作符,用于获取一个类型的所有公共属性的键的联合。它通常用于索引访问类型(indexed access types),比如对象、数组、元组等,以及类和接口的实例。

interface Point{
    x: number,
    y: number
}

// 类型P为Point的key联合类型,即:字符串 'x' | 'y'
type P = keyof Point;

const p1: P = 'x'
const p2: P = 'y'
// 如下会提示:Type "z" is not assignable to type keyof Point,因为字符串 'z' 不在类型P的范围内
// const p3: P = 'z'

/**
 * 对于元组 Tuple,keyof 返回的是索引的联合类型 "0" | "1"。
 * 对于数组 string[],keyof 返回的是 number 类型,因为数组的索引是数字。.
 */
type Tuple = [string, number];
type TupleKeys = keyof Tuple; // "0" | "1"

type ArrayKeys = keyof string[]; // number

typeof

索引访问类型

在 TypeScript 中,索引访问类型(Indexed Access Types)是一种通过索引访问来获取类型的子集的方式。这通常用于从类型中提取特定属性的类型。以下是索引访问类型的一些关键用法:

  1. 基本用法:

    type Point = {
        x: number;
        y: number;
    };
    
    // 使用索引访问类型获取属性 'x' 的类型
    type XType = Point['x']; // "number"
    
  2. 从数组中获取类型:

    type Numbers = [number, number, number];
    
    // 获取数组第一个元素的类型
    type FirstElementType = Numbers[0]; // "number"
    
  3. 从元组中获取类型:

    type Tuple = [string, number];
    
    // 获取元组第二个元素的类型
    type SecondElementType = Tuple[1]; // "number"
    
  4. 从类或接口中获取类型:

    interface Person {
        name: string;
        age: number;
    }
    
    // 获取 'name' 属性的类型
    type NameType = Person['name']; // "string"
    
  5. 使用类型守卫:

    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
        return obj[key];
    }
    
  6. 映射类型:

    type OptionsFlags<Type> = {
        [Property in keyof Type]: boolean;
    };
    
    // 假设有一个配置接口
    interface Config {
        featureX: string;
        featureY: number;
    }
    
    // 创建一个类型,其属性都是布尔类型
    type ConfigFlags = OptionsFlags<Config>;
    
  7. 条件类型:

    type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
    
    function exampleFunction(a: number, b: number) {
        return a + b;
    }
    
    // 获取函数返回类型的类型
    type FunctionReturnType = ExtractReturnType<typeof exampleFunction>; // "number"
    
  8. 模板类型参数:

    type Wrapper<T> = {
        value: T;
    };
    
    // 创建一个特定类型的 Wrapper 类型
    type StringWrapper = Wrapper<string>; // { value: string }
    

索引访问类型在 TypeScript 中非常有用,特别是在你需要根据属性名动态获取类型,或者当你需要创建基于现有类型的新类型时。通过索引访问类型,你可以构建更灵活和可重用的类型系统。

条件类型

基于条件表达式来确定类型。

/**
 * 条件类型:基于表达式来确定类型.
 */

type IdLabel = {
    id(): void
}
type NameLabel = {
    name(): void
}

// 定义泛型类型NameOrId,其类型根据参数类型T的不同,会被扩展为IdLabel或NameLabel类型
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel

function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
    throw ''
}

let label = createLabel(2);
let label1 = createLabel('hello');

type IsNumber<T> = T extends number ? "Yes" : "No";
// IsNumberResult类型为字符串No,即其只能被赋值为 No
type IsNumberResult = IsNumber<string>; // "No"
const isNumber: IsNumberResult = "No";
console.log(isNumber);

类成员

  • 构造器
  • 属性
  • 方法
class Person {
    private _name: string;
    private _age: number;

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

    // Getter for name
    get name(): string {
        return this._name;
    }

    // Setter for name
    set name(value: string) {
        this._name = value;
    }

    // Getter for age
    get age(): number {
        return this._age;
    }

    // Setter for age
    set age(value: number) {
        if (value < 0) { // 确保年龄不能为负数
            throw new Error('Age cannot be negative.');
        }
        this._age = value;
    }
}

const person = new Person("John", 30);
console.log(person.name); // Output: John
person.name = '2342';
console.log(person.name); // Output: Jane
console.log(person.age); // Output: 30
person.age = 25;
console.log(person.age); // Output: 25
person.age = -10; // Throws an error because age cannot be negative

readonly

implements

implements 关键字用于类之间的继承关系。当一个类实现(implements)一个接口时,它必须实现接口中定义的所有属性和方法。这种关系确保了类遵循了接口中定义的约定。

interface Shape {
    calculateArea(): number;
}

class Circle implements Shape {
    radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }

    calculateArea(): number {
        return Math.PI * this.radius * this.radius;
    }
}

Circle 类实现了 Shape 接口,因此必须实现 calculateArea 方法。

extends

extends 关键字用于类之间的继承关系。通过 extends 关键字,一个类可以继承另一个类的属性和方法。

class Animal {
    name: string;

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

    move(distance: number = 0) {
        console.log(`${this.name} moved ${distance}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }
}

const dog = new Dog('Buddy');
dog.move(10); // Output: Buddy moved 10m.
dog.bark(); // Output: Woof! Woof!

Dog 类通过 extends 关键字继承了 Animal 类,因此 Dog 类具有 Animal 类中的属性和方法,同时还可以定义自己的方法(如 bark 方法)。

初始化顺序

在 TypeScript 中,类的初始化顺序通常遵循以下规则:
首先,父类的构造函数会被调用。如果父类有参数,需要在子类的构造函数中调用 super() 来传递这些参数。
然后,子类自身的属性会被初始化。
最后,子类的构造函数体内的代码会被执行。

class A {
    constructor() {
        console.log("Constructing A");
    }
}

class B extends A {
    b: string;

    constructor(b: string) {
        super();
        this.b = b;
        console.log("Constructing B");
    }
}

const objB = new B("Hello");

当创建 B 类的实例 objB 时,首先会调用父类 A 的构造函数,然后初始化 B 类的属性,最后执行 B 类的构造函数体内的代码。

继承内置类型

在 TypeScript 中,你可以继承内置属性或方法,比如内置的对象、接口或类。例如,你可以继承内置的 JavaScript 对象如 Array、String 等,或者 TypeScript 中的接口和类。

下面是一个简单的示例,展示如何在 TypeScript 中继承内置的 Array 对象:

class CustomArray<T> extends Array<T> {
    constructor(...elements: T[]) {
        super(...elements);
    }

    // 自定义方法
    printSize(): void {
        console.log(`Array size: ${this.length}`);
    }
}

const customArray = new CustomArray<number>(1, 2, 3, 4);
console.log(customArray); // 输出: [1, 2, 3, 4]
customArray.printSize(); // 输出: Array size: 4

在这个示例中,CustomArray 类继承了内置的 Array 对象,并添加了一个自定义的 printSize 方法来打印数组的大小。

成员的可见性:public、protected、private

静态成员

类里的static块

class MyClass {
  // 定义静态属性
  static staticProperty: string = "Hello, I'm a static property!"

  // 定义静态方法
  static staticMethod(): string {
    return "Hello, I'm a static method!"
  }

  // 非静态属性
  instanceProperty: number

  // 构造函数
  constructor(instanceProperty: number) {
    this.instanceProperty = instanceProperty
  }

  // 实例方法
  instanceMethod(): string {
    return `The instance property is ${this.instanceProperty}`
  }
}

// 访问静态属性
console.log(MyClass.staticProperty) // 输出: Hello, I'm a static property!

// 调用静态方法
console.log(MyClass.staticMethod()) // 输出: Hello, I'm a static method!

// 如下直接通过类访问非静态属性会报错
// console.log(MyClass.instanceProperty);

类运行时的this

this 关键字的行为在类的方法中与在普通函数中的使用非常相似。this 的值取决于方法的调用方式,而不是方法定义的位置。以下是一些关于类中 this 的关键点:

1.构造函数中的 this:
在构造函数中,this 指向新创建的对象实例。

2.实例方法中的 this:
类的实例方法内部的 this 默认指向调用该方法的对象实例。

3.静态方法中的 this:
在静态方法中,this 指向类本身,而不是类的实例。

4.回调函数中的 this:
当类的方法作为回调函数传递时,this 的值将取决于回调函数的调用方式。例如,在事件处理或定时器中,this 可能指向全局对象(在浏览器中是 window)或 undefined(如果严格模式下)。

5.使用箭头函数:
箭头函数没有自己的 this 绑定,它们会捕获其所在上下文的 this 值。因此,在类的方法中使用箭头函数可能会改变期望的 this 行为。

6.方法调用:
如果方法被作为对象的方法调用,this 将指向调用它的对象。

7.call、apply 和 bind 方法:
可以使用 call、apply 或 bind 方法显式地设置 this 的值。

8.new 操作符:
当使用 new 关键字创建类的新实例时,构造函数中的 this 将自动绑定到新创建的对象。

class ThisClass {
  property = "I'm a property"

  instanceMethod() {
    console.log('instance:' + this.property) // 访问实例属性
  }

  static staticMethod() {
    console.log(this) // 指向 ThisClass 类本身
  }
}

const instance = new ThisClass()

// 构造函数中的 this
const myObject = new ThisClass()
console.log(myObject.property) // "I'm a property"

// 实例方法中的 this
instance.instanceMethod() // 输出: I'm a property

// 静态方法中的 this
ThisClass.staticMethod() // ThisClass { ... }

// 使用箭头函数改变 this
const boundMethod = () => {
  console.log('boundMethod:' + this.property) // undefined
}
boundMethod.call(this) // 输出: I'm a property,即使 boundMethod 是箭头函数

抽象类和成员

抽象类(Abstract Class)是一种不能被直接实例化的类,它主要用于基类(Base Class)的定义,可以包含抽象成员和具体成员。抽象类通常用于定义一组子类必须遵循的契约。

抽象类的关键特点:
1.使用 abstract 关键字定义:
抽象类使用 abstract class 关键字定义。

2.不能被实例化:
你不能使用 new 关键字直接实例化抽象类。

3.可以包含抽象成员和具体成员:
抽象成员是没有实现的方法(只有签名,没有函数体),子类必须实现这些抽象成员。
具体成员是具有实现的方法或属性,子类可以直接继承或重写这些成员。

4.抽象成员使用 abstract 关键字:
抽象方法或属性使用 abstract 关键字定义。

5.子类继承抽象类:
子类通过 extends 关键字继承抽象类,并提供抽象成员的具体实现。

// 定义一个抽象类
abstract class Animal {
  // 抽象属性,必须在子类中初始化
  abstract name: string;

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

  // 具体方法,子类可以继承或重写
  eat() {
    console.log("The animal eats food.");
  }
}

// 继承抽象类的子类
class Dog extends Animal {
  name = "Dog";

  makeSound() {
    console.log("Woof woof!");
  }

  walk() {
    console.log("The dog is walking.");
  }
}

// 使用抽象类
const dog = new Dog(); // 创建 Dog 类的实例
dog.eat(); // 继承自 Animal 类的 eat 方法
dog.makeSound(); // Dog 类实现的 makeSound 方法
dog.walk(); // Dog 类特有的 walk 方法

类之间的关系

在 TypeScript 中,类(Class)之间的关系可以通过几种不同的方式建立,每种关系都反映了不同的设计意图和使用场景。以下是一些常见的类关系类型:

  1. 继承(Inheritance):

    • 继承是类之间最典型的关系,允许一个类(子类或派生类)继承另一个类(基类或父类)的属性和方法。
    • 使用 extends 关键字来实现继承。
    class BaseClass {
      commonMethod() {
        console.log("Common method in base class");
      }
    }
    
    class DerivedClass extends BaseClass {
      derivedMethod() {
        console.log("Method in derived class");
      }
    }
    
  2. 实现(Implementation):

    • 类可以实现一个或多个接口(Interfaces),这要求类必须提供接口中定义的所有属性和方法的具体实现。
    • 接口是一种形式的契约,它定义了一组方法签名,但不提供实现。
    interface IPrintable {
      print(): void;
    }
    
    class PrintClass implements IPrintable {
      print() {
        console.log("Printing something");
      }
    }
    
  3. 组合(Composition):

    • 组合是指一个类包含另一个类的实例作为自己的成员,这允许类之间更灵活的关系,而不是严格的继承关系。
    • 组合有助于遵循单一职责原则,使类更加模块化。
    class Component {
      doWork() {
        console.log("Component does work");
      }
    }
    
    class CompositeClass {
      private component: Component;
    
      constructor() {
        this.component = new Component();
      }
    
      useComponent() {
        this.component.doWork();
      }
    }
    
  4. 依赖(Dependency):

    • 依赖关系发生在一个类的方法需要另一个类的实例来执行操作时,但这并不意味着有一个类“拥有”另一个类。
    • 依赖关系通常通过构造函数注入或方法参数传递来实现。
    class Dependency {
      doTask() {
        console.log("Doing a task");
      }
    }
    
    class ClientClass {
      private dependency: Dependency;
    
      constructor(dependency: Dependency) {
        this.dependency = dependency;
      }
    
      performAction() {
        this.dependency.doTask();
      }
    }
    
  5. 聚合(Aggregation):

    • 聚合是组合的一种特殊形式,其中一个类(容器类)包含另一个类的实例,但这种包含是临时的或运行时的。
    • 聚合体现了“has-a”关系,但被包含的对象可以独立存在。
    class AggregatedClass {
      private child: ChildClass;
    
      constructor(child: ChildClass) {
        this.child = child;
      }
    
      operate() {
        this.child.doSomething();
      }
    }
    
    class ChildClass {
      doSomething() {
        console.log("Doing something");
      }
    }
    
  6. 关联(Association):

    • 关联是类之间最简单的关系,其中一个类引用另一个类的对象作为其成员变量。
    • 这可以是单向或双向的,并且通常表示“has-a”关系。
    class AssociatedClass {
      private member: AnotherClass;
    
      constructor(member: AnotherClass) {
        this.member = member;
      }
    
      useMember() {
        this.member.doAction();
      }
    }
    
    class AnotherClass {
      doAction() {
        console.log("Doing an action");
      }
    }
    

理解这些类之间的关系有助于设计出更清晰、更灵活、更可维护的系统架构。每种关系都有其适用场景,选择合适的关系可以使代码更加合理和高效。

认识模块

在 TypeScript 中,模块(Modules)是一种代码组织方式,用于将相关功能封装在一起,提高代码的可维护性和可重用性。TypeScript 模块与 JavaScript 的 ES6 模块标准相似,但 TypeScript 在语法和类型系统方面提供了更多的特性。

ES模块语法

TypeScript 模块的关键特点:

  1. 命名空间(Namespaces):

    • 在 TypeScript 中,命名空间用于组织代码和避免命名冲突。
    namespace MyNamespace {
      export function sayHello() {
        console.log("Hello");
      }
    }
    
  2. 导出(Export):

    • 使用 export 关键字导出模块中的实体,如变量、函数、类和接口。
    export class MyClass {}
    
  3. 导入(Import):

    • 使用 import 关键字导入其他模块导出的实体。
    import { MyClass } from "./MyClass";
    
  4. 默认导出(Default Export):

    • 每个模块可以有一个默认导出,使用 default 关键字标记。
    // 默认导出
    export default class DefaultClass {}
    
    // 导入默认导出
    import Default from "./Default";
    
  5. 导出时重命名(Export Renaming):

    • 在导出时可以为实体指定不同的名称。
    export { myFunction as functionName };
    
  6. 导入时重命名(Import Renaming):

    • 在导入时也可以为导出的实体指定不同的名称。
    import { functionName as myFunction } from "./module";
    
  7. 模块作用域:

    • 模块内部定义的变量和函数默认是局部的,不会污染全局命名空间。
  8. 模块合并:

    • 当两个文件导入了相同的模块时,TypeScript 编译器会合并这些模块的导出。
  9. 模块路径:

    • 模块的路径可以是相对路径或绝对路径,也可以是 node_modules 中的包。
  10. 模块类型:

    • TypeScript 支持多种模块类型,包括 CommonJS、AMD、UMD、ES6 等。

示例:

// myModule.ts
export function add(x: number, y: number): number {
    return x + y;
}

export namespace myModule {
    export function multiply(x: number, y: number): number {
        return x * y;
    }

    export function divide(x: number, y: number): number {
        return x / y;
    }
}

// myApp.ts
import {myModule} from './myModule'
import {add} from "./myModule";

console.log(myModule.multiply(3, 4))
console.log(myModule.divide(3, 4))
console.log(add(3, 4))

使用模块可以使代码更加模块化,易于理解和维护。通过封装和隐藏实现细节,模块化代码减少了不同代码部分之间的耦合,提高了代码的可重用性。TypeScript 的类型系统进一步增强了模块的功能,使得在编译时就可以捕获潜在的错误。

额外的导入语法

TypeScript 提供了几种额外的导入语法,使得模块的导入更加灵活和强大。以下是一些常用的导入语法:

  1. 批量导入(Bulk Import):
    使用星号(*)可以批量导入一个模块中的所有导出项。

    import * as module from "./module";
    
  2. 命名导入(Named Import):
    指定需要导入的特定导出项。

    import { export1, export2 } from "./module";
    
  3. 默认导入(Default Import):
    导入模块的默认导出。

    import defaultExport from "./module";
    
  4. 重命名导入(Renaming Imports):
    在导入时重命名导出项或默认导出。

    // 重命名命名导出
    import { export1 as myExport1 } from "./module";
    // 重命名默认导出
    import MyDefault from "./module";
    
  5. 解构导入(Destructuring Import):
    使用解构赋值来导入对象的属性。

    import { export1, export2 } from "./module";
    const { export1, export2 } = await import("./module");
    
  6. 导入整个模块(Importing the Entire Module):
    使用 require 函数导入 CommonJS 模块。

    const module = require("module");
    
  7. 动态导入(Dynamic Import):
    使用 import() 函数进行动态导入,返回一个 Promise,该 Promise 解析为模块对象。

    if (condition) {
      import('./module')
        .then(module => module.export1())
        .catch(error => console.error(error));
    }
    
  8. 类型只导入(Type-Only Import):
    当你只关心模块的类型定义而不使用其值时,可以使用类型只导入。

    // @ts-ignore
    import type from "./module";
    
  9. 混合导入(Mixing Imports):
    在同一个 import 语句中混合使用不同的导入语法。

    import defaultExport, { export1, export2 as myExport } from "./module";
    
  10. 使用索引签名导入(Importing with Index Signature):
    当模块导出了多个项,你可以使用索引签名的方式导入。

    import module from "./module";
    // 使用索引签名访问导出项
    const export1 = module['export1'];
    

这些导入语法提供了灵活性,允许开发者根据需要选择最合适的导入方式。动态导入尤其适用于条件加载模块或按需加载,可以用于实现代码拆分和优化应用加载时间。而类型只导入则有助于减少编译后的 JavaScript 体积,因为它告诉 TypeScript 编译器只包含类型定义,不包含值。

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值