Top Advanced typescript concepts that Every Developer Should Know

typescript 是一种现代编程语言,由于其附加的类型安全性,通常比 javascript 更受青睐。在本文中,我将分享 10 个最重要的 typescript 概念,这将有助于提高您的 typescript 编程技能。准备好了吗?开始吧。

Top Advanced typescript concepts that Every Developer Should Know

1.泛型:使用泛型我们可以创建可重用的类型,这将有助于处理今天和明天的数据。
泛型示例:
我们可能想要 typescript 中的一个函数将参数作为某种类型,并且我们可能想要返回相同的类型。

function func<t>(args:t):t{
    return args;
}

2.具有类型约束的泛型:现在让我们通过定义它只接受字符串和整数来限制类型 t:

function func<t extends string | number>(value: t): t {
    return value;
}

const stringvalue = func("hello"); // works, t is string
const numbervalue = func(42);      // works, t is number

// const booleanvalue = func(true); // error: type 'boolean' is not assignable to type 'string | number'

3.通用接口:
当您想要为使用各种类型的对象、类或函数定义契约(形状)时,接口泛型非常有用。它们允许您定义一个蓝图,该蓝图可以适应不同的数据类型,同时保持结构一致。

// generic interface with type parameters t and u
interface repository<t, u> {
    items: t[];           // array of items of type t
    add(item: t): void;   // function to add an item of type t
    getbyid(id: u): t | undefined; // function to get an item by id of type u
}

// implementing the repository interface for a user entity
interface user {
    id: number;
    name: string;
}

class userrepository implements repository<user, number> {
    items: user[] = [];

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

     getbyid(idorname: number | string): user | undefined {
        if (typeof idorname === 'string') {
            // search by name if idorname is a string
            console.log('searching by name:', idorname);
            return this.items.find(user => user.name === idorname);
        } else if (typeof idorname === 'number') {
            // search by id if idorname is a number
            console.log('searching by id:', idorname);
            return this.items.find(user => user.id === idorname);
        }
        return undefined; // return undefined if no match found
    }
}

// usage
const userrepo = new userrepository();
userrepo.add({ id: 1, name: "alice" });
userrepo.add({ id: 2, name: "bob" });

const user1 = userrepo.getbyid(1);
const user2 = userrepo.getbyid("bob");
console.log(user1); // output: { id: 1, name: "alice" }
console.log(user2); // output: { id: 2, name: "bob" }

4.泛型类::当您希望类中的所有属性都遵循泛型参数指定的类型时,请使用此选项。这允许灵活性,同时确保类的每个属性都与传递给类的类型匹配。

interface user {
    id: number;
    name: string;
    age: number;
}

class userdetails<t extends user> {
    id: t['id'];
    name: t['name'];
    age: t['age'];

    constructor(user: t) {
        this.id = user.id;
        this.name = user.name;
        this.age = user.age;
    }

    // method to get user details
    getuserdetails(): string {
        return `user: ${this.name}, id: ${this.id}, age: ${this.age}`;
    }

    // method to update user name
    updatename(newname: string): void {
        this.name = newname;
    }

    // method to update user age
    updateage(newage: number): void {
        this.age = newage;
    }
}

// using the userdetails class with a user type
const user: user = { id: 1, name: "alice", age: 30 };
const userdetails = new userdetails(user);

console.log(userdetails.getuserdetails());  // output: "user: alice, id: 1, age: 30"

// updating user details
userdetails.updatename("bob");
userdetails.updateage(35);

console.log(userdetails.getuserdetails());  // output: "user: bob, id: 1, age: 35"
console.log(new userdetails("30"));  // error: "this will throw error" 

5.将类型参数约束为传递的类型:有时,我们希望参数类型依赖于其他一些传递的参数。听起来很混乱,让我们看下面的示例。

function getproperty<type>(obj: type, key: keyof type) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3 };
getproperty(x, "a");  // valid
getproperty(x, "d");  // error: argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.

6.条件类型:通常,我们希望我们的类型是一种类型或另一种类型。在这种情况下,我们使用条件类型。
一个简单的例子是:

function func(param:number|boolean){
return param;
}
console.log(func(2)) //output: 2 will be printed
console.log(func("true")) //error: boolean cannot be passed as argument

有点复杂的例子:

type hasproperty<t, k extends keyof t> = k extends "age" ? "has age" : "has name";

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

let test1: hasproperty<user, "age">;  // "has age"
let test2: hasproperty<user, "name">; // "has name"
let test3: hasproperty<user, "email">; // error: type '"email"' is not assignable to parameter of type '"age" | "name"'.

6.交集类型:当我们想要将多种类型合并为一个类型时,这些类型非常有用,允许特定类型继承各种其他类型的属性和行为。
让我们看一个有趣的例子:

// defining the types for each area of well-being

interface mentalwellness {
  mindfulnesspractice: boolean;
  stresslevel: number; // scale of 1 to 10
}

interface physicalwellness {
  exercisefrequency: string; // e.g., "daily", "weekly"
  sleepduration: number; // in hours
}

interface productivity {
  taskscompleted: number;
  focuslevel: number; // scale of 1 to 10
}

// combining all three areas into a single type using intersection types
type healthybody = mentalwellness & physicalwellness & productivity;

// example of a person with a balanced healthy body
const person: healthybody = {
  mindfulnesspractice: true,
  stresslevel: 4,
  exercisefrequency: "daily",
  sleepduration: 7,
  taskscompleted: 15,
  focuslevel: 8
};

// displaying the information
console.log(person);

7.infer 关键字:当我们想要有条件地确定特定类型时,infer 关键字很有用,并且当条件满足时,它允许我们从该类型中提取子类型。
这是一般语法:

type conditionaltype<t> = t extends sometype ? inferredtype : othertype;

示例:

type returntypeofpromise<t> = t extends promise<infer u> ? u : number;

type result = returntypeofpromise<promise<string>>;  // result is 'string'
type errorresult = returntypeofpromise<number>;      // errorresult is 'never'

const result: result = "hello";
console.log(typeof result); // output: 'string'

8.type variance:这个概念讨论了子类型和父类型如何相互关联。
它们有两种类型:
协方差:可以在需要父类型的地方使用子类型。
让我们看一个例子:

class vehicle { }
class car extends vehicle { }

function getcar(): car {
  return new car();
}

function getvehicle(): vehicle {
  return new vehicle();
}

// covariant assignment
let car: car = getcar();
let vehicle: vehicle = getvehicle(); // this works because car is a subtype of vehicle


在上面的示例中,car 继承了 vehicle 类的属性,因此将其分配给需要超类型的子类型是绝对有效的,因为子类型将具有超类型所具有的所有属性。
逆变:这与协变相反。我们在子类型应该出现的地方使用超类型。

class vehicle {
  startengine() {
    console.log("vehicle engine starts");
  }
}

class car extends vehicle {
  honk() {
    console.log("car honks");
  }
}

function processvehicle(vehicle: vehicle) {
  vehicle.startengine(); // this works
  // vehicle.honk(); // error: 'honk' does not exist on type 'vehicle'
}

function processcar(car: car) {
  car.startengine(); // works because car extends vehicle
  car.honk();        // works because 'car' has 'honk'
}

let car: car = new car();
processvehicle(car); // this works because of contravariance (car can be used as vehicle)
processcar(car);     // this works as well because car is of type car

// contravariance failure if you expect specific subtype behavior in the method


使用逆变时,我们需要小心不要访问特定于子类型的属性或方法,因为这可能会导致错误。

9。反思: 这个概念涉及在运行时确定变量的类型。虽然 typescript 主要关注编译时的类型检查,但我们仍然可以利用 typescript 运算符在运行时检查类型。
typeof 运算符:我们可以利用 typeof 运算符在运行时查找变量的类型

const num = 23;
console.log(typeof num); // "number"

const flag = true;
console.log(typeof flag); // "boolean"

instanceof 运算符:instanceof 运算符可用于检查对象是否是类或特定类型的实例。

class vehicle {
  model: string;
  constructor(model: string) {
    this.model = model;
  }
}

const benz = new vehicle("mercedes-benz");
console.log(benz instanceof vehicle); // true

我们可以使用第三方库在运行时确定类型。

10.依赖注入:依赖注入是一种模式,允许您将代码引入组件中,而无需在组件中实际创建或管理它。虽然它看起来像是使用库,但它有所不同,因为您不需要通过 cdn 或 api 安装或导入它。

乍一看,它似乎也类似于使用函数来实现可重用性,因为两者都允许代码重用。然而,如果我们直接在组件中使用函数,可能会导致它们之间的紧密耦合。这意味着函数或其逻辑的任何变化都可能影响它使用的每个地方。

依赖注入通过将依赖项的创建与使用它们的组件解耦来解决这个问题,使代码更易于维护和测试。

没有依赖注入的示例

// health-related service classes without interfaces
class mentalwellness {
  getmentalwellnessadvice(): string {
    return "take time to meditate and relax your mind.";
  }
}

class physicalwellness {
  getphysicalwellnessadvice(): string {
    return "make sure to exercise daily for at least 30 minutes.";
  }
}

// healthadvice class directly creating instances of the services
class healthadvice {
  private mentalwellnessservice: mentalwellness;
  private physicalwellnessservice: physicalwellness;

  // directly creating instances inside the class constructor
  constructor() {
    this.mentalwellnessservice = new mentalwellness();
    this.physicalwellnessservice = new physicalwellness();
  }

  // method to get both mental and physical wellness advice
  gethealthadvice(): string {
    return `${this.mentalwellnessservice.getmentalwellnessadvice()} also, ${this.physicalwellnessservice.getphysicalwellnessadvice()}`;
  }
}

// creating an instance of healthadvice, which itself creates instances of the services
const healthadvice = new healthadvice();

console.log(healthadvice.gethealthadvice());
// output: "take time to meditate and relax your mind. also, make sure to exercise daily for at least 30 minutes."

依赖注入示例

// Health-related service interfaces with "I" prefix
interface IMentalWellnessService {
  getMentalWellnessAdvice(): string;
}

interface IPhysicalWellnessService {
  getPhysicalWellnessAdvice(): string;
}

// Implementations of the services
class MentalWellness implements IMentalWellnessService {
  getMentalWellnessAdvice(): string {
    return "Take time to meditate and relax your mind.";
  }
}

class PhysicalWellness implements IPhysicalWellnessService {
  getPhysicalWellnessAdvice(): string {
    return "Make sure to exercise daily for at least 30 minutes.";
  }
}

// HealthAdvice class that depends on services via interfaces
class HealthAdvice {
  private mentalWellnessService: IMentalWellnessService;
  private physicalWellnessService: IPhysicalWellnessService;

  // Dependency injection via constructor
  constructor(
    mentalWellnessService: IMentalWellnessService,
    physicalWellnessService: IPhysicalWellnessService
  ) {
    this.mentalWellnessService = mentalWellnessService;
    this.physicalWellnessService = physicalWellnessService;
  }

  // Method to get both mental and physical wellness advice
  getHealthAdvice(): string {
    return `${this.mentalWellnessService.getMentalWellnessAdvice()} Also, ${this.physicalWellnessService.getPhysicalWellnessAdvice()}`;
  }
}

// Dependency injection
const mentalWellness: IMentalWellnessService = new MentalWellness();
const physicalWellness: IPhysicalWellnessService = new PhysicalWellness();

// Injecting services into the HealthAdvice class
const healthAdvice = new HealthAdvice(mentalWellness, physicalWellness);

console.log(healthAdvice.getHealthAdvice());
// Output: "Take time to meditate and relax your mind. Also, Make sure to exercise daily for at least 30 minutes."


在紧密耦合的场景中,如果您今天 mentalwellness 类中有一个stresslevel 属性,并决定明天将其更改为其他属性,则需要更新所有使用它的地方。这可能会导致许多重构和维护挑战。

但是,通过依赖注入和接口的使用,你可以避免这个问题。通过构造函数传递依赖项(例如 mentalwellness 服务),具体的实现细节(例如stresslevel 属性)被抽象到接口后面。这意味着只要接口保持不变,对属性或类的更改不需要修改依赖类。这种方法可确保代码松散耦合、更易于维护且更易于测试,因为您可以在运行时注入所需的内容,而无需紧密耦合组件。

以上就是Top Advanced typescript concepts that Every Developer Should Know的详细内容,更多请关注硕下网其它相关文章!