Как указать тип функции, у которого объект параметра имеет ключ и разрешает любые другие ключи? - PullRequest
0 голосов
/ 22 марта 2020

С помощью машинописного текста мы можем определить интерфейс для объекта, который должен иметь ключ и может дополнительно разрешить любые другие ключи:

interface ObjectWithTrace {
  trace: string;
  [index: string]: any
}
const traced: ObjectWithTrace = { trace: 'x', foo: 'bar' }; // looks good
const untraced: ObjectWithTrace = { foo: 'bar' }; // Error: Property 'trace' is missing in type '{ foo: string; }' but required in type 'ObjectWithTrace'. ts(2741)   

В приведенном выше примере trace является обязательным ключ. Мы можем добавить любые ключи, которые мы хотим, к объекту, и пока ключ trace определен, машинопись является счастливой. Perfect

Теперь при попытке расширить этот лог c для применения к аргументам функции возникает ошибка:

type FunctionWithParamWithTrace = (args: {
  trace: string;
  [index: string]: any
}) => any;
const doSomethingAndTrace: FunctionWithParamWithTrace = (args: { trace: string }) => {}; // looks good
const doSomethingElseAndTrace: FunctionWithParamWithTrace = (args: { trace: string, foo: 'bar' }) => {} /*
 Error: Type '(args: { trace: string; foo: "bar"; }) => void' is not assignable to type 'FunctionWithParamWithTrace'.
  Types of parameters 'args' and 'args' are incompatible.
    Property 'foo' is missing in type '{ [index: string]: any; trace: string; }' but required in type '{ trace: string; foo: "bar"; }'.ts(2322)
*/

Похоже, я что-то упустил. Есть ли способ определить тип для функции, которая должна иметь только параметр, и позволить этому параметру разрешать любые ключи с любыми значениями, а также требовать, чтобы один ключ существовал отдельно (например, свойство с именем trace)?

Я надеюсь поддержать следующее:

const doSomethingWithTrace: FunctionWithParamWithTrace = (args: { trace: string, foo: string }) => {}; // looks good
const doSomethingWithoutTrace: FunctionWithParamWithTrace = (args: { foo: string }) => {} // Error: Property 'trace' is missing in type '{ foo: string; }'...

1 Ответ

1 голос
/ 22 марта 2020

То, что вам нужно, - это возможность расширять нужный тип по подтипам. Это может быть достигнуто введением обобщенных параметров c:

type FunctionWithParamWithTrace<Args extends {
  trace: string;
  [index: string]: any
}> = (args: Args) => any;

const doSomethingAndTrace: FunctionWithParamWithTrace<{ trace: string }> 
= (args) => {}; // looks good
const doSomethingElseAndTrace: FunctionWithParamWithTrace<{ trace: string, foo: 'bar' }> 
= (args) => {} // works

Причина, по которой вы не можете использовать более строгий тип в качестве аргумента, видна в следующем коде:

type FunctionWithParamWithTrace = (args: {
  trace: string;
  [index: string]: any
}) => any;

type FunctionWithMoreStrictParameters = (args: { trace: string, foo: string }) => any

type NoItDoesNotExtends 
= FunctionWithMoreStrictParameters extends FunctionWithParamWithTrace 
? true 
: false // false

Это означает, что ваша более строгая функция не является подмножеством исходной, поэтому ее нельзя использовать как таковую. Это почему? Представьте, что у вас есть функция, которая работает с более точными аргументами, например, функция, которая всегда получает foo, эта функция не может быть использована вместо функции, которая может принимать любой объект. Вот почему это не может быть выполнено.


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

function f(arg: {a: string, b: string | number}) {
    return arg.b.concat('b cannot be used as string as it can be number') // error
}

function g(arg: {a: string, b: string}) {
    return arg.b.concat(' b can be used as string') // pass compilation
}

Функция g тип аргумента является подмножеством функции f тип аргумента, поэтому можно использовать вместо f? Скорее наоборот, вместо g можно использовать функцию f, поскольку она может работать с более широким шрифтом и означает, что ее тело должно выполнять дополнительные проверки. Это видно в функции f, мы не можем просто использовать string | number как string, нам нужно выполнить дополнительные проверки значения. Но когда мы смотрим на g, ему не нужно делать проверки, поскольку он работает только с string. Что бы тогда произошло, если бы мы использовали g вместо f и получили бы аргумент number, это, конечно, время выполнения cra sh.

В итоге тип функции - это подмножество другой тип функции, если он более гибкий в типах аргументов, не более строгий.

...