有没有一种方法可以根据泛型类型中的条件使Typescript键成为可选的?

yqkkidmi  于 2023-06-24  发布在  TypeScript
关注(0)|答案(1)|浏览(190)

我想创建一个通用的TS类型,给定一个现有的接口返回一个接口,使所有最后一级属性可选,所以给定以下类型

interface FormDeep {
    email: string;
    address: {
        street: {
            line1: string;
            line2: string;
        }
        town: string;
    }
}

此类型应有效

const newAddress: DeepPartialLastLevel<FormDeep> = {
    email: undefined,
    address: {
        street: {
            line1: undefined,
            line2: undefined
        },
        town: undefined,  
    }
}

但这个不应该

const newAddressNotValid: DeepPartialLastLevel<FormDeep> = {
    email: undefined,
    address: {
        street: undefined, // street is not last level so it should be mandatory
        town: undefined,  
    }
}

我已经成功地创建了一个这样做的类型

type DeepPartialLastLevel<T> = {
  [K in keyof T]: T[K] extends object ? DeepPartialLastLevel<T[K]> : (T[K] | undefined)
}

当我不希望密钥被指定时,问题就出现了(VS被定义并且等于undefined)。
总结如下:我希望下面的对象有效

const newAddress2: DeepPartialLastLevel<FormDeep> = {
    email: undefined,
    address: { // TS complains because town is missing
        street: {
            line1: undefined,
            line2: undefined
        },
    }
}

我需要的属性是可选的基础上的条件T[K] extends object的东西一样

[K in keyof T as T[K] extends object? [K]: [K]?]: T[K] extends object ? DeepPartialLastLevel<T[K]> : T[K]

但从TS语法的观点来看这是无效的
我创建了一个简单的TSPlayground来再现

ryhaxcpt

ryhaxcpt1#

算法:

  • 仅循环遍历对象字段并递归调用它们的类型
  • 循环遍历其余字段并将其标记为可选
  • 返回对象字段和非对象字段的交集

要循环遍历字段,我们将使用mapped types,并使用键重Map过滤掉需要的字段。基本上,如果字段不是一个对象,我们将其键never,这将完全删除字段:

type DeepPartialLastLevel<T> = {
    [K in keyof T as T[K] extends object ? K : never]: DeepPartialLastLevel<T[K]>
}

使用inject关键字,我们将在推断类型OnlyObjects中存储上一个Map类型的结果:

type DeepPartialLastLevel<T> = {
    [K in keyof T as T[K] extends object ? K : never]: DeepPartialLastLevel<T[K]>
} extends infer OnlyObjects
? // do something
: never // in theory will be reached

现在,我们将再次使用mapped typesMap对象的键(不包括OnlyObjects的键),这是使用内置的实用工具类型Exclude完成的,为了将它们标记为可选,我们将使用?可选属性修饰符:

{
   [K in Exclude<keyof T, keyof OnlyObjects>]?: T[K];
}

把一切都放在一起:

type DeepPartialLastLevel<T> = {
    [K in keyof T as T[K] extends object ? K : never]: DeepPartialLastLevel<T[K]>
} extends infer OnlyObjects
    ? OnlyObjects & {
        [K in Exclude<keyof T, keyof OnlyObjects>]?: T[K];
    }
    : never;

排除部分也可以使用内置的实用程序类型Partial和Omit来完成:

type DeepPartialLastLevel<T> = {
    [K in keyof T as T[K] extends object ? K : never]: DeepPartialLastLevel<T[K]>
} extends infer OnlyObjects
    ? OnlyObjects & Partial<Omit<T, keyof OnlyObjects>>
    : never;

他们中的任何一个都像预期的那样工作。
测试:

interface FormDeep {
    email: string;
    address: {
        street: {
            line1: string;
            line2: string;
        };
        town: string;
    };
}

const newAddressNotValid: DeepPartialLastLevel<FormDeep> = {
    email: undefined,
    address: {
        street: undefined,
        town: undefined,
    },
};

// This one should be valid
const newAddress: DeepPartialLastLevel<FormDeep> = {
    email: undefined,
    address: {
        street: {
            line1: undefined,
            line2: undefined,
        },
        town: undefined,
    },
};

//This one should also be valid
const newAddress2: DeepPartialLastLevel<FormDeep> = {
    email: undefined,
    address: {
        street: {
            line1: undefined,
            line2: undefined,
        },
    },
};

链接到Playground

相关问题