You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现支持排除指定属性的TypeScript至少一个属性必填工具类型

TypeScript 实现支持排除属性的「至少一个必填」工具类型

问题描述

现有全可选属性的TypeScript接口MyInterface,已通过CustomRequired工具类型实现「至少一个属性必填」的约束。扩展得到NewInterface并新增可选属性property4后,需要实现新约束:必须包含property1/property2/property3中的至少一个,property4可选但无法满足必填要求。希望修改或扩展CustomRequired,使其支持传入排除属性的联合类型,同时保留原有无需排除参数的使用方式。

背景

原接口与工具类型实现:

interface MyInterface {
    property1?: string;
    property2?: string;
    property3?: string; // 注:原拼写proeprty3修正为property3
}

// 原实现:要求至少一个属性必填
type AtLeastOnePropRequired<T> = { [P in keyof T]-?: Record<P, T[P]> }[keyof T];
type CustomRequired<T> = T & AtLeastOnePropRequired<T>;

使用示例:

// 要求property1/property2/property3中至少一个必填
CustomRequired<MyInterface>;

需求场景

扩展接口后:

interface NewInterface extends MyInterface {
    property4?: boolean;
}

原CustomRequired<NewInterface>会将property4也纳入必填判断范围,不符合需求。需要让property4仅作为可选属性,不参与「至少一个必填」的校验。

解决方案

修改CustomRequired,添加可选的排除属性泛型参数,默认值设为never(表示不排除任何属性)。核心逻辑是先排除指定属性,再对剩余属性应用「至少一个必填」约束,最后与原类型交叉:

type CustomRequired<T, Excluded extends keyof T = never> = 
  T & (
    // 筛选出需要强制至少一个必填的属性(排除指定属性)
    { [P in Exclude<keyof T, Excluded>]-?: Record<P, T[P]> }[Exclude<keyof T, Excluded>]
  );

类型逻辑说明

  1. Exclude<keyof T, Excluded>:从T的属性键中移除需要排除的属性,得到需要参与「至少一个必填」校验的属性集合。
  2. { [P in ...]-?: Record<P, T[P]> }:为每个待校验属性生成一个仅该属性必填的类型。
  3. [...]Exclude<keyof T, Excluded>]:取上述所有类型的联合,确保至少有一个待校验属性必填。
  4. 与原类型T交叉:保留所有属性的可选性,同时强制待校验属性中至少一个存在。

示例验证

场景1:无排除属性(原有逻辑)

// 要求NewInterface中任意属性至少一个必填
type Case1 = CustomRequired<NewInterface>;

// 合法:包含property4
const validCase1: Case1 = { property4: true };
// 非法:无任何属性
const invalidCase1: Case1 = {}; // 报错

场景2:排除property4

// 要求必须包含property1/property2/property3中的至少一个,property4可选
type Case2 = CustomRequired<NewInterface, 'property4'>;

// 合法:包含property2和property4
const validCase2: Case2 = { property2: "test", property4: false };
// 非法:仅包含property4
const invalidCase2: Case2 = { property4: false }; // 报错

场景3:排除多个属性

// 要求必须包含property2/property3中的至少一个,排除property1和property4
type Case3 = CustomRequired<NewInterface, 'property1' | 'property4'>;

// 合法:包含property3
const validCase3: Case3 = { property3: "demo" };
// 非法:仅包含property1和property4
const invalidCase3: Case3 = { property1: "hi", property4: true }; // 报错

替代方案(不修改原CustomRequired)

若希望保留原CustomRequired,可单独创建新工具类型:

type CustomRequiredExclude<T, Excluded extends keyof T> = 
  T & AtLeastOnePropRequired<Omit<T, Excluded>>;

内容的提问来源于stack exchange,提问作者pez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 10:41:36