是否可以在 Typescript 中获取记录值的数组类型?
Is it possible to get an array types of the record's values in Typescript?
我想获取所有类型的记录值。例如,如果我有这样的对象:
{
'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[]
约束用于所有对象函数中的第二个参数。这是一种以某种方式推断类型和 return 比 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()
强类型化它的输出,它不仅在T
中必须是generic,context
的类型,而且在与 objectWithFunctions
中的方法 name/argument 映射相关的类型参数。我在这里的方法是使新类型参数 A
成为一个对象类型,其键是方法名称,其值是参数列表,不包括 [=27= 类型的初始 context
参数].
例如,对于 obj
,A
将指定为
{
log: [msg: string];
add: [value: number];
}
请注意,您实际上永远不会使用 A
类型的值,但可以从 objectWithFunctions
输入中推断出它,并且可以直接表示 objectWithFunctions
的类型] 和 return 类型 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 where the array A[K]
in each property K
of A
is transformed to a function type with an argument of type T
followed by a rest argument of type A[K]
. It turns out that because this type is of the form {[K in keyof A]...}
, it is a homomorphic mapped type and the compiler is good at inferring from homomorphic mapped types。 return 类型,如 result
的注释中给出的,是没有初始 T
参数的相同映射类型。
在函数体内我不得不使用一些 type assertions to convince the compiler that some values had certain types. The initial value of result
is an empty object, so I had to assert that it would be the final type (because {}
certainly isn't of that type). And the return type of Object.keys()
is just string[]
(see ) 所以我不得不断言它 return 是一个 keyof A
.
的数组
好的,我们来测试一下:
const context: MyContext = { store: [] };
将此处的 context
注释为 MyContext
很有用,否则编译器会假定 store
将始终为空数组(never[]
类型)。这里是:
const newObj = convert(context, obj);
您可以通过 IntelliSense 使用 Quickinfo 查看对 convert()
的调用导致编译器为 T
推断出 MyContext
并为 [= 推断出上述 { log: [msg: string]; add: [value: number];}
类型30=]:
/* 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]
我想获取所有类型的记录值。例如,如果我有这样的对象:
{
'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[]
约束用于所有对象函数中的第二个参数。这是一种以某种方式推断类型和 return 比 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()
强类型化它的输出,它不仅在T
中必须是generic,context
的类型,而且在与 objectWithFunctions
中的方法 name/argument 映射相关的类型参数。我在这里的方法是使新类型参数 A
成为一个对象类型,其键是方法名称,其值是参数列表,不包括 [=27= 类型的初始 context
参数].
例如,对于 obj
,A
将指定为
{
log: [msg: string];
add: [value: number];
}
请注意,您实际上永远不会使用 A
类型的值,但可以从 objectWithFunctions
输入中推断出它,并且可以直接表示 objectWithFunctions
的类型] 和 return 类型 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 where the array A[K]
in each property K
of A
is transformed to a function type with an argument of type T
followed by a rest argument of type A[K]
. It turns out that because this type is of the form {[K in keyof A]...}
, it is a homomorphic mapped type and the compiler is good at inferring from homomorphic mapped types。 return 类型,如 result
的注释中给出的,是没有初始 T
参数的相同映射类型。
在函数体内我不得不使用一些 type assertions to convince the compiler that some values had certain types. The initial value of result
is an empty object, so I had to assert that it would be the final type (because {}
certainly isn't of that type). And the return type of Object.keys()
is just string[]
(see keyof A
.
好的,我们来测试一下:
const context: MyContext = { store: [] };
将此处的 context
注释为 MyContext
很有用,否则编译器会假定 store
将始终为空数组(never[]
类型)。这里是:
const newObj = convert(context, obj);
您可以通过 IntelliSense 使用 Quickinfo 查看对 convert()
的调用导致编译器为 T
推断出 MyContext
并为 [= 推断出上述 { log: [msg: string]; add: [value: number];}
类型30=]:
/* 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]