Как правильно ввести и привести общий в TypeScript?

#typescript #casting #typescript-generics

Вопрос:

Я не знаю, как правильно задать вопрос, поскольку я не понимаю, как работает ввод текста с обобщенными текстами в TypeScript, но я знаю, как это объяснить, например, предполагая, что у меня есть «хранилище функций»:

 const functionStore: Array <Function> = [];
 

И у меня есть функция для добавления функций в хранилище функций, эта функция позволяет получить объект и указать имя ключей функций, которые будут добавлены к объекту, который передается параметром:

 const addFunction = <Obj = { [index: string]: any }>(
    obj: Obj,
    ...extractFunction: Array<keyof Obj>
) => {
    for (const key of extractFunction) {
        functionStore.push(obj[key]);
    }
};
 

Поэтому, если я создам следующий объект и передам его addFunction :

 const obj = {
    msg: 'Welcome!',
    test() {
        console.log('Hello!!');
    },
    doNoAdd() {
        console.log('Do not add!');
    },
    otherTest() {
        console.log('Other test!!');
    }
};

addFunction(obj, 'test', 'otherTest');
 

Это не работает, так как «Аргумент типа» Obj[keyof Obj] не может быть присвоен параметру типа « Function «:

 ...
for (const key of extractFunction) {
    functionStore.push(obj[key]); //<-- Error!!
}
...
 

И если я выполню приведение, оно все равно выдаст ошибку, поскольку, будучи универсальным, это свойство может быть string , number , и т.д. array (И я думаю, что может быть лучшая альтернатива вместо приведения к unknown или any сначала):

 ...
functionStore.push(obj[key] as Function); //<-- Conversion of type 'Obj[keyof Obj]' to type 
                                          //     'Function' may be a mistake because neither
                                          //     type sufficiently overlaps with the other. 
                                          //     If this was intentional, convert the
                                          //      expression to 'unknown' first.
...
 

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

Я надеюсь, что вы сможете мне помочь 🙂

Ответ №1:

Чтобы сделать его немного безопаснее, вы можете добавить больше ограничений Obj .

 type Fn = (...args: any[]) => any

const functionStore: Array<Fn> = [];


type ExtractFn<T> = {
    [Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]

const addFunction = <Obj extends Record<string, any>>(
    obj: Obj,
    ...extractFunction: Array<ExtractFn<Obj>>
) => {
    for (const key of extractFunction) {
        functionStore.push(obj[key]);
    }
};
const obj = { name: 'John', getName: () => 'John' }

addFunction(obj, 'getName') // ok
addFunction(obj, 'name') // expected error
 

Игровая площадка
Как вы могли заметить, вам не разрешено указывать ключ, который не соответствует функции.

ОБЪЯСНЕНИЕ

[Prop in keyof T] — см. Сопоставленные типы. Он перебирает каждое свойство в T типе. Это похоже for..in на цикл в javascript.

T[Prop] — см. Индексированный доступ. type A={age:42}; type B = A['age'] // 42 . Работает так же, как и в javascript.

T[Prop] extends Fn ? Prop : never — см. Раздел Условные типы. Это означает, что if T[Prop] является подтипом Fn -> return Prop , в противном случае — never .

Следовательно:

 type ExtractFn<T> = {
    [Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}
 

Перебирает тип и проверяет, является ли свойство функцией или нет. Если это функция, замените эту функцию на имя свойства, в противном случае — на never .

И последнее, используя [keyof T] :

 type ExtractFn<T> = {
    [Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]
 

Без keyof T , ExtractFn возвращает:

 ExtractFn<{msg:'string', doThing:()=>any}> // {msg:never, doThing:'doThing'}
 

Итак, нам нужно получить объединение всех значений. Это легко сделать keyof T . На самом деле, он возвращает never | 'doThing' . Поскольку never присваивается любому типу, never | 'doThing' он сталкивается с «doThing».

Пример:

 type Foo={age:42,name:string}['age'|'name'] // 42 | name
 

Надеюсь, теперь вам стало намного понятнее.

Комментарии:

1. Где в руководстве вы объясняете этот тип обобщенной структуры? Я вообще не могу этого понять 🙂

2. @Grizzly добавил объяснение

3. Спасибо за объяснение.

Ответ №2:

Проблема заключается в том, как определяется общий тип, = оператор присваивает тип по умолчанию, который, вероятно, никогда не произойдет, потому что он всегда выводится TS из аргумента type Obj . Что бы это исправить, так это использовать extend оператор, который будет ограничивать аргумент для расширения предоставляемого интерфейса.

вот так:

 // this is pretty cool
const functionStore: Array <Function> = [];

// so it should extend the interface that you have defined
const addFunction = <Obj extends { [index: string]: any }>(
    obj: Obj,
    ...extractFunction: Array<keyof Obj>
) => {
    for (const key of extractFunction) {
        functionStore.push(obj[key]);
    }
};

 

Это должно сделать это — игровая площадка


Просто чтобы добавить, если вы посмотрите на ссылку playground и наведите курсор на addFunction вызов, вы заметите, что общий тип определяется указанным вами аргументом. Вот документация по общим ограничениям.

Комментарии:

1. Я не знал, что его можно расширить, и что тип его выводит, спасибо.