是否可以在 Typescript 中获取记录值的数组类型?

Is it possible to get an array types of the record's values in Typescript?

我想获取所有类型的记录值。例如,如果我有这样的对象:

{
  'a': 1,
  'b': false
}

我想要一个包含 numberboolean 的类型。

我正在尝试在不丢失类型的情况下实现某种函数映射:

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);

Playground

此实现有效,但它缺少类型,因为 any[] 约束用于所有对象函数中的第二个参数。这是一种以某种方式推断类型和 return 比 Record<string, (...args: any[]) => void> 更严格的类型的方法吗?

首先,您不能 annotate obj 作为 Record<string, (context: MyContext, ...args: any[]) => void> 类型而不丢弃有关初始化程序中特定方法名称和参数的所有信息。如果你想让 convert(context, obj) 的输出类型知道 logadd,那么你应该只分配给 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中必须是genericcontext的类型,而且在与 objectWithFunctions 中的方法 name/argument 映射相关的类型参数。我在这里的方法是使新类型参数 A 成为一个对象类型,其键是方法名称,其值是参数列表,不包括 [=27= 类型的初始 context 参数].

例如,对于 objA 将指定为

{
    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]

Playground link to code