无法扩展TypeScript类的泛型

kuarbcqp  于 2023-01-14  发布在  TypeScript
关注(0)|答案(2)|浏览(171)

我很难理解TypeScript泛型与类的这种行为。

打字本

interface IProvider<K extends {[key: string]: any}> {
  data: K;
}

class Provider<T extends {[key: string]: any}> implements IProvider<T> {
  data: T;
  
  constructor(arg?: T) {
    this.data = arg || {}; // This is not allowed.
  }
}

type User = {
  [key: string]: any
}

const x = new Provider<User>();

错误是:

Type 'T | {}' is not assignable to type 'T'.  
'T | {}' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ [key: string]: any; }'.  
Type '{}' is not assignable to type 'T'.
      '{}' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ [key: string]: any; }'.

但是,如果我删除了可选操作符,它就能正常工作。

打字本

class Provider<T extends {[key: string]: any}> implements IProvider<T> {
  data: T;
  
  constructor(arg: T) { // no optional 
    this.data = arg || {}; // Now it works.
  }
}

请帮我解释一下,非常感谢!

798qvoo8

798qvoo81#

这个错误正确地警告你潜在的不合理性。
考虑下面的场景,其中User类型有一个string类型的属性a,当arg是可选的,我们不需要传递任何对象给构造函数,构造函数将用{}初始化数据。
访问x.data.a将导致运行时值为undefined,即使我们将其键入为string

type User = {
  a: string
}

const x = new Provider<User>();

x.data.a.charCodeAt(0) // runtime Error!

如果我们将构造函数参数设为强制性的,就不会发生这种情况。
Playground

vhmi4jdf

vhmi4jdf2#

今天是你的幸运日。
我不想谈论为什么它不工作,但解释泛型的概念,为什么,以及我们应该在哪里使用它。

1-行为

例如,我有三个对象,

  • 产品
  • 用户
  • 接触

我有一个Printer类,它可以打印任何实现如下Printable接口的对象。

export interface Printable {
  print(): string;
}

export interface Printer<T extends Printable> {
  print(obj: T): string;
}

export class BlackWhitePrinter<T extends Printable> {
  print(obj: T) {
    return `[BlackWhitePrinter] ` + obj.print();
  }
}

export class ColorPrinter<T extends Printable> {
  print(obj: T) {
    return `[Color Printer] ` + obj.print();
  }
}

export class Product implements Printable {
  readonly name: string = 'product name';
  print() {
    return this.name;
  }
}

export class User implements Printable {
  readonly username: string = 'username';
  print() {
    return this.username;
  }
}

export class Contact implements Printable {
  readonly phone: string = '+1 999 999 99 99';
  print() {
    return this.phone;
  }
}

const blackWhitePrinter = new BlackWhitePrinter();
const colorPrinter = new BlackWhitePrinter();

blackWhitePrinter.print(new User());
blackWhitePrinter.print(new Product());
blackWhitePrinter.print(new Contact());

colorPrinter.print(new User());
colorPrinter.print(new Product());
colorPrinter.print(new Contact());

2-数据和行为

interface PhoneNumber {
  phoneNumber?: string;
}

interface EmailAddress {
  email?: string;
}

interface CanCall {
  call(contact: PhoneNumber): void;
}

interface CanEmail {
  email(contact: EmailAddress): void;
}

interface Contact extends PhoneNumber, EmailAddress {}

interface ContactWithAddress extends Contact {
  address?: string;
}

/**
 * Android phone can call and send email
 */
export class AndroidPhone<T extends ContactWithAddress>
  implements CanCall, CanEmail
{
  constructor(public readonly contacts: T[]) {}

  call(contact: PhoneNumber): void {
    console.log(`Call to ${contact.phoneNumber}`);
  }
  email(contact: EmailAddress): void {
    console.log(`Email to ${contact.email}`);
  }
}

/**
 * Regular phone can call only
 */
export class RegularPhone<T extends PhoneNumber> implements CanCall {
  constructor(public readonly contacts: T[]) {}
  call(contact: PhoneNumber): void {
    console.log(`Calling to ${contact.phoneNumber}`);
  }
}

/**
 * Unfortunately, some people only have regular phones.
 */
class PoorUser {
  constructor(public readonly phone: CanCall) {}
}

/**
 * Some dudes, always use the last vertion of XYZ Smart phones
 */
class RichUser<T extends CanCall & CanEmail> {
  constructor(public readonly phone: T) {}
}

const poorUser = new PoorUser(
  new RegularPhone([{ phoneNumber: '+1 999 999 99 99' }])
);

/**
 * Even after we give a smart phone to poor people, they cannot send emails because they do not have internet connection :(
 */
const poorUser1 = new PoorUser(
  new AndroidPhone([{ phoneNumber: '+1 999 999 99 99' }])
);

/**
 * Hopefully, they can call if they paid the bill.
 */
poorUser.phone.call({ phoneNumber: '+1 999 999 99 99' });
// poorUser1.phone.email({email:'.......'}) // Cannot send email because he is not aware of the future!

/**
 * Rich people neither call nor email others because they are always busy and they never die.
 */
const richUser = new RichUser(
  new AndroidPhone([
    { email: 'email@email.com', phoneNumber: '+1 999 999 99 99' },
  ])
);

/**
 * Another rich call.
 */
richUser.phone.call({ phoneNumber: '+1 999 999 99 99' });

/**
 * Another rich email. If you are getting lots of emails, it means you are 
 * poor because rich people do not open their emails, their employees do.
 * I've never seen any rich googling or searching in StackOverflow "How to replace Money with Gold?", probably they search things like that. Did you see any?
 */
richUser.phone.email({ email: 'email@email.com' });

相关问题