首页 > 解决方案 > 打字稿条件验证取决于字段可空性

问题描述

我确实有以下代码:

type DomainFieldDefinition<T> = {
  required?: boolean
}

type DomainDefinition<F, M> = {
  fields?: { [K in keyof F]: DomainFieldDefinition<F[K]> },
  methods?: { [K in keyof M]: M[K] & Function },
}

type User = {
  id: string,
  name?: string
}

export const User = createDomain<User>({
  fields: {
    id: { required: true },
    name: {},
  },
});

我正在尝试验证required传递给createDomain字段方法的定义对象中的键是否具有与其所基于的类型的必需性相匹配的值(此处,User);理想情况下在编译时。

我感觉条件类型可能有助于这样做,但我找不到基于必要性的方法。具体来说,我试图限制required为:

有什么提示吗?

标签: typescript

解决方案


使用此处定义的类型作为示例,我们可以创建一个条件类型,如果该字段是必需的,则field类型将是类型{ required : true }{}其他类型:

type DomainDefinition<F, M> = {
    fields?: {
        [K in keyof F]: ({} extends { [P in K]: F[K] } ? {} : { required: true }) & {} // Intersect with other properties as necessary
    },
    methods?: { [K in keyof M]: M[K] & Function },
}

type User = {
    id: string,
    name?: string
}

function createDomain<T>(o: DomainDefinition<T, any>) {
    return o;
}

export const User = createDomain<User>({
    fields: {
        id: { required: true },
        name: {},
    },
});

注意这将测试可选性(?修饰符)它不会测试可空性(| null | undefined)取决于您的用例,这可能重要也可能不重要。

同样有趣的可能是这个答案,readonly它对修饰符进行了测试。使用它,您还可以添加一个isReadonly字段:

type IfEquals<X, Y, A, B> =
    (<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? A : B;

type DomainDefinition<F, M> = {
    fields?: {
        [K in keyof F]:
        ({} extends { [P in K]: F[P] } ? {} : { required: true })
        & IfEquals<{ [P in K]: F[P] }, { -readonly [P in K]: F[P] }, {}, { isReadonly: true }>
    },
    methods?: { [K in keyof M]: M[K] & Function },
}

type User = {
    id: string,
    readonly name?: string
}

function createDomain<T>(o: DomainDefinition<T, any>) {
    return o;
}

export const User = createDomain<User>({
    fields: {
        id: { required: true },
        name: { isReadonly: true },
    },
});

如果要过滤掉某些属性,例如函数,则必须将所有出现的 替换F为过滤的F。为了简单起见,只需定义一个额外的类型别名:

type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
type DomainPropertyHelper<F> = {
  [K in keyof F]: ({} extends { [P in K]: F[K] } ? {} : { required: true }) & {} // Intersect with other properties as necessary
}; 
type DomainDefinition<F, M> = {
    fields?: DomainPropertyHelper<Pick<F, NonFunctionPropertyNames<F>>>,
    methods?: { [K in keyof M]: M[K] & Function },
}

推荐阅读