【问题标题】:Advanced TS Types: Wrapper type that mirrors keys but becomes new value高级 TS 类型:镜像键但成为新值的包装器类型
【发布时间】:2021-06-14 23:04:39
【问题描述】:

我正在尝试编写一个需要一些接口的类型定义,如下所示:

interface IMyInterface {
   name: string
   subobject: {
     boolField: boolean
   }
}

然后让OptionsTransformer 包装它,对于每个key-value 对,保持键相同,但将值更改为新类FieldOptions<type>。例如:

const transformed: OptionsTransformer<IMyInterface> = {
     name: new FieldOptions<string>(),
     subobject: {
         boolField: new FieldOptions<boolean>()
     }
}

这是因为我知道TS 接口在运行时会消失,因此您无法在运行时真正检查它们。我希望能够编写如下函数(我的实际用例是在发布请求的正文中输入:我知道它应该是什么样子,并想验证它看起来像那样):

const runtimeVerifier<T> = (
   options: OptionsTransformer<T>, input: Record<string, unknown>
) => {
   //for each value in input, run the it's verifier. If it's a sub-object,
   //recurse and do runtime verifier on the sub-object.
}

另一个要求是,在编译时,如果我向IMyInterface 添加一个字段,我希望TS 编译器给我一个错误,例如“Your optionsTransformer is missing the field 。”。

我对这个定义已经很远了:

//this class doesn't matter, but here for completeness
class FieldOption<T> {}

export interface IValidBody {
    [x: string]: boolean | string | number | IValidBody 
}

export type OptionsTransformer<T extends IValidBody> = {
    [key in keyof T]-?: T[key] extends IValidBody 
        ? IOptionsForFields<T[key]> 
        : FieldOption<T[key]>
}

但我在子对象上遇到了一些非常复杂的错误,并且无法破译它们。

任何帮助将不胜感激。

【问题讨论】:

  • 你试过什么? OptionsTransformer FieldOption 在哪里?
  • @AlirezaAhmadi 抱歉,OptionsTransformer 接口在“我尝试过的部分”中命名错误。我在编辑中重命名了它。 FieldOption 无关紧要,但为了清楚起见,我也添加了它。
  • 不将其发布为答案,因为它回避了您的实际问题,但您可能需要考虑现有的对象验证库,如 joiajv。两者都完全支持 TypeScript。

标签: typescript typescript-typings typescript-generics


【解决方案1】:

经过几天的破解,我找到了解决方案。

我非常接近 - 让 OptionsTransformer 中的泛型强制递归子类型也实现接口 - 即使它实现了,类型也被删除了(出于我不知道的原因)然后它 没有't 实现类型。

解决方案是让OptionsTransformer generic 不扩展子类型。

最终的解决方案如下所示,为清晰起见对类进行了重命名:

//can be any class!
class AnyClass<T> {
    constructor(item: T) {
        console.log(typeof item)
    }
}

//just using string here, but can be any type(s).
interface RecursiveInterface {
    [key: string]: string | RecursiveInterface
}

//fix is here: T no longer extends Recursive interface. 
//I'm not 100% sure why, but it works. Any sub object will be 
//"duck typed" into recursive interface, so you can nest as deeply
//as you desire. 
type RecursiveInterfaceWrappedWithClass<T> = {
    [key in keyof T]: T[key] extends RecursiveInterface ? RecursiveInterfaceWrappedWithClass<T[key]> : AnyClass<T[key]>
}


//example: interface with nested object. Only go 1 deep in this example,
//but you can nest as far as you'd like.
interface MyObject {
    foo: string
    bar: {
        baz: string
    }
}


//here's the wrapper.
const myObjectWrapped: RecursiveInterfaceWrappedWithClass<MyObject> = {
    foo: new AnyClass('string'),
    bar: {
        baz: new AnyClass('hi')
    }
}

【讨论】:

    猜你喜欢
    • 2022-08-20
    • 2013-05-24
    • 2016-06-16
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 2021-10-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多