TypeScript type可将对象类型的某些属性动态标记为;“必需”;及;定义为;?

TypeScript type可将对象类型的某些属性动态标记为;“必需”;及;定义为;?,typescript,generics,type-inference,Typescript,Generics,Type Inference,我一直在尝试为下面的函数useDefaults提供泛型类型: type ValuesOf=T扩展只读任何[]?T[数字]:从不; 需要和定义的类型={ [K中的P]-?:排除; }; 导出类型EnforcedDefaultProps=K从不扩展?P:省略、要求和定义; 导出类型ArrayEnforcedDefaultProps=EnforcedDefaultProps; export const usefaults=( 道具:P |未定义, defaultProps:ArrayEnforcedD

我一直在尝试为下面的函数
useDefaults
提供泛型类型:

type ValuesOf=T扩展只读任何[]?T[数字]:从不;
需要和定义的类型={
[K中的P]-?:排除;
};
导出类型EnforcedDefaultProps=K从不扩展?P:省略、要求和定义;
导出类型ArrayEnforcedDefaultProps=EnforcedDefaultProps;
export const usefaults=

( 道具:P |未定义, defaultProps:ArrayEnforcedDefaultProps, 强制执行的错误?:只读阵列, ):ArrayEnforcedDefaultProps=>{ const newProps:P={…defaultProps,…props}; 如果(enforcedDefaults){//如果用户显式地将undefined传递给prop,则除非密钥位于enforcedDefaults中,否则不会使用默认的prop 强制执行错误 .filter((键)=>newProps[key]==未定义) .forEach((key)=>newProps[key]=defaultProps[key]); } 将新道具作为ArrayEnforcedDefaultProps返回; };

此函数将对两个对象执行简单的基于赋值的操作:一个包含用户提交的选项,另一个包含function developer设置的默认值。然后,它将选择性地对某些属性强制执行非未定义的值(function developer提供),以便
useDefaults
的返回对象的类型消除了这些特定属性的值为
未定义的可能性。这可以防止函数开发人员不得不使用非空断言,但也不会给函数用户带来负担

所需用途示例:

接口格式选项{
maxLength?:数字,
前缀?:字符串,
后缀?:字符串,
}
常量格式=(值:字符串,_opts?:FormatOpts)=>{
const opts=usefaults(_opts{
前缀:“”,
后缀:“”,
},[“前缀”,“后缀]);
//前缀和后缀此时保证不为“未定义”,但函数的用户不需要在“\u opts”对象中明确指定
const modifiedPrefix=prefix.toUpperCase();//希望避免像prefix!.toUpperCase()这样的事情;
}
//以“前缀”道具为焦点的示例:
格式(“ASDF”);//确定->使用默认值后的前缀:“
格式(“ASDF”,{prefix:“MyPrefix”});//确定->使用默认值后的前缀:“MyPrefix”
格式(“ASDF”,{prefix:undefined});//确定->使用默认值后的前缀:“
格式(“ASDF”,{后缀:“test”});//确定->使用默认值后的前缀:“
我希望使它尽可能动态(尽可能多的类型)

虽然这段代码确实在编译,但不知何故,打字是关闭的。它不是在需要的地方删除带有
undefined
s的单个对象类型,而是看起来是每个可能组合的并集。有没有一种更简单的方法来做我想做的事情,或者将这些组合变平

截图的类型与生成的截图完全相同:

  • 我想要的#2类型是

    {
    只读maxLength?:字符串|未定义;
    只读前缀:字符串;
    只读后缀:字符串;
    }
    
    在阅读@jcalz建议并进行更多实验/简化后,这几乎是动态地生成正确的键入:

    类型.ts:

    从“ts essentials”导入{StrictOmit};
    需要和定义的导出类型={
    [K中的P]-?:排除;
    };
    导出类型MappedObjValue={
    [K输入A键和B键]:
    A[K]扩展B[K]
    从不
    :K
    };
    导出类型OptionalKeys=(MappedObjValue)[keyof T];
    导出类型keyworkeysof

    =(ReadonlyArray)|(keyof P); 导出类型ExtractArrayItem=T扩展只读阵列?U:T; 导出类型EnforcedDefaultProps< P扩展对象, K扩展(keyworkeysof

    |未定义), EK=提取阵列项目 >=[EK]扩展[keyof P]?严格省略、要求和定义:P; 导出类型EnforcedDefaultPropsInput< P扩展对象, K扩展(keyworkeysof |未定义), OP扩展对象=拾取, EK=提取阵列项目 >=[EK]扩展[keyof OP]?强化道具:OP;

    useDefaults.ts:

    export const usefaults=

    ( 道具:P, defaultProps:EnforcedDefaultPropsInput, 强制执行错误?:ED, ):EnforcedDefaultProps=>{ const newProps:P={…defaultProps,…props}; 如果(enforcedDefaults){//如果用户显式地将undefined传递给prop,则除非密钥位于enforcedDefaults中,否则不会使用默认的prop 强制执行错误 .filter((键)=>newProps[key]==未定义) .forEach((键)=>{ newProps[key]=(defaultProps as any)[key];//暂时转换为any }); } 将新道具作为EnforcedDefaultProps返回; };

    用法.ts:

    接口是共享格式{
    前缀?:字符串,
    后缀?:字符串,
    }
    导出接口IStringFormatOpts扩展ISharedFormatOpts{
    maxLength?:数字,
    }
    导出常量defaultStringFormatOpts:DeepReadonly={
    前缀:“”,
    后缀:“”,
    };
    // ...
    const options=useDefaults(选择IStringFormatOpts、defaultStringFormatOpts、[“前缀”、“后缀”]);
    
    选项的解析类型为

    Pick&required和defined
    

    如果具有默认值的对象是内联的,那么它也可以正常工作,这样就不需要类型化常量。

    在阅读@jcalz建议并进行更多实验/简化后,这几乎可以动态地生成正确的类型:

    类型.ts:

    从“ts essentials”导入{StrictOmit};
    需要和定义的导出类型={
    [K中的P]-?:排除;
    };
    导出类型MappedObjValue={
    [K输入A键和B键]:
    A[K]扩展B[K]
    从不
    :K
    };
    导出类型OptionalKeys=(MappedObjValue)[keyof T];
    导出类型KeyWorkerySof