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

标签 typescript typescript-generics

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

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

Playground

此实现有效,但缺少类型,因为 any[]约束用于所有对象函数中的第二个参数。这是一种以某种方式推断类型并返回更严格的类型而不是 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()要强类型化它的输出,它必须是 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]在每个属性中 KA转换为参数类型为 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]

Playground link to code

关于typescript - 是否可以在 Typescript 中获取记录值的数组类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69135444/

相关文章:

node.js - 如何在 typescript 中使用 Node 配置?

json - TS2307 : Cannot find module or its corresponding type declarations

TypeScript:如何创建只读数字索引对象

javascript - 使用 Route Resolve 进行 Angular 服务器端渲染

angular - 如何在外部配置文件中定义变量并在 app.module 中使用它

javascript - 为什么我不能将 typescript 泛型类型分配给该类型的联合?

TypeScript:类型实例化太深并且可能是无限的

typescript - 在 TypeScript 中,当类型是函数的参数时,有没有办法限制 Partial<T> 类型的额外/多余属性?

javascript - Typescript嵌套函数包装返回类型问题

typescript 在编译时减去两个数字