функция с неизвестным параметром не может быть назначена функции со строковым параметром - PullRequest
0 голосов
/ 20 марта 2020

Я использую машинопись и определил следующий интерфейс:

export interface BaseProtocol {
    [key: string]: (payload: unknown) => Promise<unknown>;
}

Теперь я хотел бы определить протокол:

import { BaseProtocol } from './BaseProtocol';

export interface AppProtocol extends BaseProtocol {
    action(payload: string): Promise<string>;
}

К сожалению, я получаю следующую ошибку:

Property 'action' of type '(payload: string) => Promise<string>' is not assignable to string index type '(payload: unknown) => Promise<unknown>'.ts(2411)

Я не желаю использовать любые, потому что это наносит ущерб безопасности типов использования протокола. Любая идея, как я могу решить эту проблему?

1 Ответ

2 голосов
/ 20 марта 2020

Ошибка допустимая; если у вас есть интерфейсы A и B extends A, то если я хочу A, вы можете передать мне B. Но с вашими определениями это ломается:

function callBaseProtocol(bp: BaseProtocol) {
    return bp.action(12345);
}

function callAppProtocol(ap: AppProtocol) {
    callBaseProtocol(ap); // oops!
}

Функция callBaseProtocol() не ошибается. Определение BaseProtocol говорит о том, что каждый строковый ключ - это функция, которая принимает значение типа unknown и возвращает Promise<unknown>. В частности, свойство action для BaseProtocol должно быть функцией, которая принимает значение типа unknown. Поэтому вы можете передать его number.

Функция callAppProtocol() также не содержит ошибок. Здесь мы называем callBaseProtocol() и передаем AppProtocol. Это должно быть приемлемо, потому что AppProtocol extends BaseProtocol.

Но если AppProtocol имеет свойство action, которое ожидает string и вызывает некоторый метод string -specifi c, например x => x.toUpperCase(), это взорвется. Ошибка в том, что AppProtocol, на самом деле, неправильно расширяет BaseProtocol.


Это, возможно, удивительное явление известно как параметр контравариантности , где, если X является подтипом Y, то функция (y: Y)=>any является подтипом (x: X)=>any, и не наоборот . Направление подтипирования идет другим путем для параметров функции, и это означает "противоположность".

Это поведение было введено в TypeScript с помощью опции компилятора --strictFunctionTypes и является улучшением безопасности типов.


Так как вы можете справиться с этим? Если вы хотите повысить безопасность типов и не снизить безопасность типов (вы упомянули, что не хотите использовать any), то, возможно, ваш BaseProtocol не совсем правильно определен. Возможно, вместо подписи строкового индекса и передачи unknowns, это должен быть интерфейс generi c, который представляет более сложные отношения между вводом и выводом. Возможно, что-то вроде этого:

export type BaseProtocol<T> = {
    [K in keyof T]: (payload: T[K]) => Promise<T[K]>;
}

Это говорит о том, что BaseProtocol зависит от другого типа T, и что BaseProtocol<T> имеет метод для каждого свойства T, который принимает параметр типа значения этого свойства и возвращает Promise этого типа.

И тогда AppProtocol может быть просто определен как:

export interface AppProtocol extends BaseProtocol<{ action: string }> {
}

Это решает проблему из ранее, так как компилятор не позволит вам вызвать метод BaseProtocol<T> action() для аргумента number, если только он не знает, что T имеет свойство action типа number:

function callBaseProtocol<T>(bp: BaseProtocol<T>) {
    return bp.action(12345); // error! might not have an action
}

Существует также гибридный подход, который не предполагает, что возвращаемое значение каждой функции является обещанием того же типа, что и вход:

export type BaseProtocol<T> = {
    [K in keyof T]: (payload: T[K]) => unknown;
}

export interface AppProtocol extends BaseProtocol<{ action: string }> {
    action(x: string): Promise<string>;
}

Хорошо, надеюсь что помогает; удачи!

Детская площадка ссылка на код

...