【问题标题】:Recursive Type Mapping in TypescriptTypescript 中的递归类型映射
【发布时间】:2020-08-14 13:39:04
【问题描述】:

我正在尝试从包含本机类型验证函数的对象中提取类型映射,例如,如果我有该对象

const test  = {
  Test: {
    Test1: {
      Test2: SimpleStringValidator //return type is string or undefined but input can be anything
    },
  }
}

我要提取类型

type Extracted = {
  Test: {
    Test1: {
      Test2: string
    }
  }
}

为此,我编写了以下代码来提取包含一组嵌套验证函数的对象的返回类型

Sample.ts


export type Validator<T> = NativeTypeValidator<T> | ObjectValidator<T>

export type NativeTypeValidator<T> = (n: any) => T | undefined
export type ObjectValidator<O> = {
  [K in keyof O]: Validator<O[K]> 
}

//native validators
export const SimpleStringValidator:NativeTypeValidator<string> = (val) => typeof(val) === "string" ? val : undefined

//object validator function
export const ObjValidator = <V>(validatorObj: ObjectValidator<V>) => (o:any):V =>{
  let result = {} as V;
  //we can only validate objects
  if (typeof (o) !== "object") { return undefined; }
  const validatorKeys = Object.keys(o) as [keyof ObjectValidator<V>]
  validatorKeys.forEach((validatorKey) => { 
    const objValue = o[validatorKey] as V[keyof V];
    const objectValidator = validatorObj[validatorKey]
    if (!objectValidator) { return undefined } //do nothing if no validator exists for the key in o
    //figure out if we have a nested object validator or a native validator at the corresponding key of validatorObj
    if (typeof (objectValidator) === "object") {
      result[validatorKey] = ObjValidator(objectValidator as ObjectValidator<V[keyof V]>)(objValue)
    }
    else {
      const nativeValidator = objectValidator as NativeTypeValidator<V[keyof V]>;
      result[validatorKey] = nativeValidator(objValue)
    }
  })
  return result;
}

export const test  = {
  Test: {
    Test1: {
      Test2: SimpleStringValidator
    },
  }
}

export const validatorFunc = ObjValidator(test);
export const outputExample = validatorFunc({
  Test: {
    Test1: {
      Test2: "hi"
    },
  }
})

outputExample.Test.Test1.Test2 = "1";
outputExample.Test.Test1.Test2 = 1; //vs code intellisense complains because needs to be type string

在我的例子中,intellisense 自动完成嵌套属性 Test2 并将类型作为字符串,但是当我创建类型声明文件时,类型信息是不同的,我无法将此生成的类型正确导出到其他项目。具体来说,它将属性 Test1 的类型设置为 any。我要生成的类型是上面代码中validatorFunc的输出和outputExample的类型。

生成的样本.d.ts

export declare type Validator<T> = NativeTypeValidator<T> | ObjectValidator<T>;
export declare type NativeTypeValidator<T> = (n: any) => T | undefined;
export declare type ObjectValidator<O> = {
    [K in keyof O]: Validator<O[K]>;
};
export declare const SimpleStringValidator: NativeTypeValidator<string>;
export declare const ObjValidator: <V>(validatorObj: ObjectValidator<V>) => (o: any) => V;
export declare const test: {
    Test: {
        Test1: {
            Test2: NativeTypeValidator<string>;
        };
    };
};
export declare const validatorFunc: (o: any) => {
    Test: {
        Test1: any;
    };
};
export declare const outputExample: {
    Test: {
        Test1: any;
    };
};

我需要使用根验证器对象作为我提取的类型的源,以寻找生成的声明文件具有正确类型的解决方案。我在 Typescript 操场上有这段代码,它显示了自动完成和放入 .d.ts 文件的内容之间的区别。

TypescriptPlayground example

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    如果要提取:

    {
      Test: {
        Test1: {
          Test2: string
        }
      }
    }
    

    您可以使用这种递归类型映射,使用infer

    type ValidatedObject<T> = Partial<{
      [key in keyof T]: T[key] extends ObjectValidator<infer Type>
        ? ValidatedObject<Type>
        : T[key] extends NativeTypeValidator<infer Type>
        ? Type
        : T[key] extends object
        ? ValidatedObject<T[key]>
        : T[key];
    }>
    

    Playground

    注意:partial有两个原因,一是类型完全输出到.d.ts文件,二是undefined返回值。

    【讨论】:

    • 谢谢你,我有一种感觉 infer 会在这个答案中发挥作用。我的实际用例有点复杂,所以我采用了这个答案和 playgroud 示例,并尝试添加对数组的支持。我真的很喜欢打字稿,但它很快就会变得相当复杂,而不是编写简单的 javascript,我正在与我自己做的令人满意的外来类型映射作斗争。
    • 一个问题,如果我将简单的验证器从使用泛型字符串更改为布尔值或数字,这将不再有效。我希望这对数字和布尔值都有效,但显然我错了。
    • @MacD,显然 TypeScript 决定将除 strings 之外的任何内容与 ObjectValidator 匹配,我更新了定义并且它现在可以正常工作(更新了答案中的 TS Playground 链接)
    • 谢谢你,你的 Typescript 技能太棒了。关于如何像在本示例中那样学习调整打字稿或在遇到问题时更好地对其进行故障排除的任何建议。这些答案都不是特别明显,并且很难维护一些脆弱且在很大程度上不直观的东西(至少对我和我目前的熟练程度而言),尤其是在你拥有大量 Typescript 的共享代码库中开发人员的熟练程度。
    • @MacD,我的建议是两件事,首先是了解这些类型的工作原理(在这种情况下是递归),因为您可以使用的工具集有限,您必须将它们结合起来告诉打字稿你想要什么。我的另一个建议是在编写函数之前定义你的类型,并确保你的泛型尽可能窄。 PS:一个小小的奖励提示,如果你使用 Utility Types,打字稿会全面评估你在 .d.ts 和 itelisense 中的类型。
    猜你喜欢
    • 1970-01-01
    • 2020-04-06
    • 2018-05-30
    • 1970-01-01
    • 2018-04-04
    • 2017-08-08
    • 2019-03-21
    • 2018-02-05
    • 1970-01-01
    相关资源
    最近更新 更多