Как заставить следующие функции вызываться с правильным типом? - PullRequest
0 голосов
/ 13 апреля 2020

Я пытаюсь сделать следующее:

class Comm {
  _type:string
}

class EMail extends Comm {
  _type = "email"
  email: string
}

class Phone extends Comm {
  _type = "phone"
  phone:number
}

function registerCallback(_type:string, (comm:  Comm) => void){}

registerCallback("email", (e: EMail)=>{}) // Correct
registerCallback("phone", (e: EMail)=>{}) // Should be error

Как сделать этот тип API безопасным для вызова функции регистра с правильной парой типа и обратного вызова, который работает с правильным классом?

Кстати, я не могу использовать _type: "email" | "phone", так как люди должны иметь возможность добавлять свои собственные обработчики без изменения типа базового кода.

class Serial extends Comm {
  _type = "serial"
  ...
}
registerCallback("serial", (s: Serial)=>{})
// Should be OK without modifying any base files

Возможно ли это с помощью параметризованных объектов и ввода в Typescript?

1 Ответ

0 голосов
/ 13 апреля 2020

У вас есть несколько синтаксических ошибок в вашем коде, возможно, это было просто ради примера? Это должно выглядеть примерно так, чтобы работать правильно (без каких-либо изменений в намерениях):

class Comm {
  _type: string;
}

class EMail extends Comm {
  _type = 'email';
  email: string;
}

class Phone extends Comm {
  _type = 'phone';
  phone: number;
}

function registerCallback(_type: string, fn: (comm: Comm) => void) {}

registerCallback('email', (e) => {}); // Correct
registerCallback('phone', (e) => {}); // Should be error

Вы можете сделать это несколькими различными способами. Одним из простых способов было бы объявить enum и использовать его для строгого контроля над тем, какие значения _type могут иметь (объявление его как _type: string недостаточно для указания c):

One possible solution using enums

Теперь вы получаете сообщение об ошибке каждый раз, когда пытаетесь использовать неправильную комбинацию _type и fn формы обратного вызова. Однако это довольно хрупко и легко сломается, если вы внесете какие-либо изменения в определения типов. Это также не очень DRY, и вам придется многократно повторять определения registerCallback, если вы хотите его перегрузить. Кроме того, каждый раз, когда вы вызываете registerCallback, вы должны передавать _type и fn как два отдельных аргумента, которые кажутся довольно избыточными.

Элегантное решение здесь - использовать Generics. Вы можете определить типы и registerCallback таким образом, чтобы тип был определен в время использования заранее, но все еще имеет строгие защитные ограждения относительно того, как его можно использовать:

A better solution using Generics

class Comm {}

class EMail extends Comm {
  email: string;
}

class Phone extends Comm {
  phone: number;
}

type CommUnion = EMail | Phone;

function registerCallback<T extends CommUnion>(fn: (e: T) => void) {}

registerCallback((e: EMail) => e.email); // Correct
registerCallback((e: Phone) => e.phone); // Correct
registerCallback<EMail>((e) => e.email); // Correct
registerCallback<Phone>((e) => e.phone); // Correct
registerCallback((e: { phone: 123456789 }) => e.phone); // Correct
registerCallback((e: { email: 'me@me.com' }) => e.email); // Correct
registerCallback((e: { email: 'me@me.com' }) => e.phone); // Should be error (types don't match)
registerCallback<Phone>((e) => e.email); // Should be error (types don't match)
registerCallback((e: { foo: 'foo' }) => e.foo); // Should be error (e is improper type)
registerCallback((e) => e.email); // Should be error (no type specified)

Посмотрите, сколько чище это выглядит! Я настоятельно рекомендую подход Generi c, если вы можете. Они могут быть немного хитрыми, поэтому прочитайте его, чтобы узнать больше о том, как он работает и чувствовать себя комфортно. :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...