#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. Я не знал, что его можно расширить, и что тип его выводит, спасибо.