我想获取所有类型的记录值。例如,如果我有这样一个对象:
{
'a': 1,
'b': false
}
我想要一个包含 number
的类型和 boolean
.
我正在尝试在不丢失类型的情况下实现某种函数映射:
type MyContext = { store: number[]; };
const obj: Record<string, (context: MyContext, ...args: any[]) => void> = {
log(ctx: MyContext, msg: string) {
console.log(msg);
},
add(ctx: MyContext, value: number) {
ctx.store.push(value);
}
};
const convert = <TContext>(context: TContext, objectWithFunctions: Record<string, (context: TContext, ...args: any[]) => void>) => {
//omit the first parameter and return a new object with modified functions
const result: Record<string, (...args: any[]) => void> = {};
Object.keys(objectWithFunctions).forEach((functionName) => {
const func = objectWithFunctions[functionName];
result[functionName] = (...args) => func(context, ...args);
});
return result;
};
const context = { store: [] };
const newObj = convert(context, obj);
newObj.log('some message');
newObj.add(12);
newObj.add();//should give an error
newObj.foo();//should give an error
console.log(newObj, context.store);
此实现有效,但缺少类型,因为 any[]
约束用于所有对象函数中的第二个参数。这是一种以某种方式推断类型并返回更严格的类型而不是 Record<string, (...args: any[]) => void>
的方法吗? ?
最佳答案
首先,你不能annotate obj
作为 Record<string, (context: MyContext, ...args: any[]) => void>
类型而不会丢弃有关初始化程序中特定方法名称和参数的所有信息。如果你想要 convert(context, obj)
的输出类型了解log
和 add
, 那么你应该只分配给 obj
完全没有注释;让编译器推断其类型:
const obj = {
log(ctx: MyContext, msg: string) {
console.log(msg);
},
add(ctx: MyContext, value: number) {
ctx.store.push(value);
}
};
稍后,如果编译器没有提示调用convert(context, obj)
, 那么你知道 obj
是合适的类型。
接下来,为了convert()
要强类型化它的输出,它必须是 generic不仅在 T
, context
的类型, 但也在与 objectWithFunctions
中的方法名称/参数映射相关的类型参数中.我在这里的方法是制作新的类型参数 A
是一个对象类型,其键是方法名称,其值是不包括初始 context
的参数列表T
类型的参数.
例如,对于 obj
, A
将被指定为
{
log: [msg: string];
add: [value: number];
}
请注意,您实际上永远不会使用 A
类型的值, 但可以从 objectWithFunctions
中推断出来输入,并且可以直接表示 objectWithFunctions
的类型和返回类型 A
.以下是打字:
const convert = <T, A extends Record<keyof A, any[]>>(
context: T,
objectWithFunctions: { [K in keyof A]: (context: T, ...rest: A[K]) => void }
) => {
const result = {} as { [K in keyof A]: (...args: A[K]) => void };
(Object.keys(objectWithFunctions) as Array<keyof A>)
.forEach(<K extends keyof A>(functionName: K) => {
const func = objectWithFunctions[functionName];
result[functionName] = (...args) => func(context, ...args);
});
return result;
};
所以,objectWithFunctions
的类型是 mapped type其中数组 A[K]
在每个属性中 K
的 A
转换为参数类型为 T
的函数类型后跟 A[K]
类型的剩余参数.结果是因为这个类型的形式是{[K in keyof A]...}
, 它是一个同态映射类型并且编译器擅长 inferring from homomorphic mapped types .返回类型,在 result
的注释中给出, 是没有初始 T
的相同映射类型争论。
在函数体内我不得不使用一些type assertions说服编译器某些值具有某些类型。 result
的初始值是一个空对象,所以我不得不断言它是最终类型(因为 {}
肯定不是那种类型)。和 Object.keys()
的返回类型只是string[]
(参见 Why doesn't Object.keys return a keyof type in TypeScript? )所以我不得不断言它返回一个 keyof A
的数组.
好吧,让我们测试一下:
const context: MyContext = { store: [] };
注释context
很有用这里是MyContext
, 因为否则编译器假定 store
将始终是一个空数组(类型为 never[]
)。这里是:
const newObj = convert(context, obj);
您可以通过 IntelliSense 使用 Quickinfo 查看对 convert()
的调用导致编译器推断出 MyContext
对于 T
和前面提到的{ log: [msg: string]; add: [value: number];}
输入 A
:
/* const convert: <MyContext, {
log: [msg: string];
add: [value: number];
}>(context: MyContext, objectWithFunctions: {
log: (context: MyContext, msg: string) => void;
add: (context: MyContext, value: number) => void;
}) => {
...;
} */
当您将鼠标悬停在 myObj
上时,你可以看到它正是你想要的类型:
/* const newObj: {
log: (msg: string) => void;
add: (value: number) => void;
} */
所以它的行为符合预期:
newObj.log('some message');
newObj.add(12);
newObj.add(); // error
newObj.foo(); // error
console.log(newObj, context.store); // {}, [12]
关于typescript - 是否可以在 Typescript 中获取记录值的数组类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69135444/