typescript 如何创建需要设置单个属性的类Partial-like

rkkpypqq  于 2022-12-14  发布在  TypeScript
关注(0)|答案(7)|浏览(164)

我们有一个如下所示的结构:

export type LinkRestSource = {
    model: string;
    rel?: string;
    title?: string;
} | {
    model?: string;
    rel: string;
    title?: string;
} | {
    model?: string;
    rel?: string;
    title: string;
};

这几乎等于说

type LinkRestSource = Partial<{model: string, rel: string, title: string}>

只不过这将允许传入一个空对象,而初始类型要求传入一个属性
我如何创建一个像Partial这样的泛型,但它的行为与上面的结构类似?

mklgxw1f

mklgxw1f1#

我想我有一个解决方案给你。你正在寻找一个接受T类型并产生一个包含T * 至少一个 * 属性的相关类型的东西。也就是说,它类似于Partial<T>,但排除了空对象。
如果是这样的话,这里是:

type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]

要剖析它:首先,AtLeastOne<T>Partial<T>something 的交集。U[keyof U]意味着它是U的所有属性值的并集。(的默认值)U设置为Map类型,其中T的每个属性都Map到Pick<T, K>Pick<T, K>是键K的单属性类型。(例如,Pick<{foo: string, bar: number},'foo'>等效于{foo: string}...它从原始类型中“挑选”'foo'属性。)这意味着在这种情况下,U[keyof U]T中所有可能的单属性类型的并集。
嗯,这可能会让人感到困惑。让我们一步一步地看看它是如何在下面的具体类型上操作的:

type FullLinkRestSource = {
  model: string;
  rel: string;
  title: string;
}

type LinkRestSource = AtLeastOne<FullLinkRestSource>

扩展到

type LinkRestSource = AtLeastOne<FullLinkRestSource, {
  [K in keyof FullLinkRestSource]: Pick<FullLinkRestSource, K>
}>

type LinkRestSource = AtLeastOne<FullLinkRestSource, {
  model: Pick<FullLinkRestSource, 'model'>,
  rel: Pick<FullLinkRestSource, 'rel'>,
  title: Pick<FullLinkRestSource, 'title'>
}>

type LinkRestSource = AtLeastOne<FullLinkRestSource, {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}>

type LinkRestSource = Partial<FullLinkRestSource> & {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}[keyof {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}]

type LinkRestSource = Partial<FullLinkRestSource> & {
  model: {model: string},
  rel: {rel: string},
  title: {title: string}>
}['model' | 'rel' | 'title']

type LinkRestSource = Partial<FullLinkRestSource> &
  ({model: string} | {rel: string} | {title: string})

type LinkRestSource = {model?: string, rel?: string, title?: string} & 
  ({model: string} | {rel: string} | {title: string})

type LinkRestSource = { model: string, rel?: string, title?: string } 
  | {model?: string, rel: string, title?: string} 
  | {model?: string, rel?: string, title: string}

我想这正是你想要的。
您可以测试一下:

const okay0: LinkRestSource = { model: 'a', rel: 'b', title: 'c' }
const okay1: LinkRestSource = { model: 'a', rel: 'b' }
const okay2: LinkRestSource = { model: 'a' }
const okay3: LinkRestSource = { rel: 'b' }
const okay4: LinkRestSource = { title: 'c' }

const error0: LinkRestSource = {} // missing property
const error1: LinkRestSource = { model: 'a', titel: 'c' } // excess property on string literal

你觉得这样行吗?祝你好运!

0aydgbwb

0aydgbwb2#

如果你知道你想要的属性,还有另一个解决方案。

type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>

这也将允许您锁定同一类型的多个密钥,例如

type LinkRestSource = AtLeast<T, 'model' | 'rel'>
bihw5rsg

bihw5rsg3#

jcalz提供的解决方案的简化版本:
type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
所以整个实现变成了

type FullLinkRestSource = {
  model: string;
  rel: string;
  title: string;
}

type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
type LinkRestSource = AtLeastOne<FullLinkRestSource>

const okay0: LinkRestSource = { model: 'a', rel: 'b', title: 'c' }
const okay1: LinkRestSource = { model: 'a', rel: 'b' }
const okay2: LinkRestSource = { model: 'a' }
const okay3: LinkRestSource = { rel: 'b' }
const okay4: LinkRestSource = { title: 'c' }

const error0: LinkRestSource = {} // missing property
const error1: LinkRestSource = { model: 'a', titel: 'c' } // incorrectly spelled property

这是TSPlayground的链接,可以尝试一下

whhtz7ly

whhtz7ly4#

不幸的是,上面的答案对我不起作用。
这可能是因为编译器无法捕获错误,也可能是因为我的IDE无法检索对象的预期属性,即使对象的类型已被注解。
以下运行良好,取自official microsoft azure/keyvault-certificates软件包:

type RequireAtLeastOne<T> = { [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>; }[keyof T]
egdjgwm8

egdjgwm85#

也许是这样的:

type X<A, B, C> = (A & Partial<B> & Partial<C>) | (Partial<A> & B & Partial<C>) | (Partial<A> & Partial<B> & C);
type LinkRestSource = X<{ model: string }, { rel: string }, { title: string }>
var d: LinkRestSource = {rel: 'sdf'};

但是有点乱:)

type Y<A, B, C> = Partial<A & B & C> & (A | B | C);
neekobn8

neekobn86#

另一种方法,如果你需要保留一些必需的属性和至少一个其余的必需太。见打字脚本Playground的例子。
基本界面可能如下所示:

export interface MainData {
    name: string;
    CRF: string;
    email?: string;
    cellphone?: string;
    facebookId?: string;
  }

......如果您只需要“email”、“cellphone”和“facebookId”中的至少一个,请更改和合并每个属性的接口,而不使用可选符号:

export interface registByEmail extends Omit<MainData, 'email'> { email: string }
export interface registByCellphone extends Omit<MainData, 'cellphone'> { cellphone: string }
export interface registByFacebook extends Omit<MainData, 'facebookId'> { facebookId: string }

export type RegistData = registByCellphone | registByEmail | registByFacebook

结果如下所示:

// language throws error
let client: RegistData = { name, CRF }
// its ok
let client: RegistData = { name, CRF, email }
let client: RegistData = { name, CRF, cellphone }
let client: RegistData = { name, CRF, facebookId }
let client: RegistData = { name, CRF, email, cellphone }
dced5bon

dced5bon7#

在我的例子中,我希望至少有一个属性是***实际设置的***(而不仅仅是从并集中提取的,其中一些路径的值是设计的undefined)。
我能想出的最简单的公式是...

type SomePropertyFrom<T> = { [K in keyof T]: Pick<Required<T>, K> }[keyof T]

我没有发现上面的任何一种更简洁的方法在处理像{concurrent:number} | {concurrent?:never}这样的复杂联合体时起作用,更冗长的联合体看起来很可怕,我宁愿完全理解我的类型。
我的方法收敛于gafi的解决方案的一个变体,即type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T],但关键是我从Required<T>中选取,否则undefined仍然在来自我的类型联合的有效属性值集中(因此它仍然不排除空对象)。
应该可以单独使用上面的方法,但是下面给出了我定义一个非空ScheduleOptions类型的完整解决方案(由一个复杂的联合体支持),作为参考。这个例子还显示了AllOrNothing<T>的定义,它可能是这类问题的补充类型...

/** Allows ConcurrencyLimit properties, or IntervalLimit properties or both, but requires at least one group to be fully set */
type ScheduleOptions = SomeLimit & {
  errorHandler?: (err: unknown) => unknown;
};

/** A limit on the number of pending promises (created but not yet settled) */
interface ConcurrencyLimit {
  concurrency: number;
}

/** A limit on the number of promises created within a millisecond interval */
interface IntervalLimit {
  intervalCap: number;
  intervalMs: number;
}

/** Allow any limit to be set or unset (implicitly includes case of no limits set, which we will exclude in the next step) */
type AnyLimit = AllOrNothing<ConcurrencyLimit> & AllOrNothing<IntervalLimit>;

/** Require at least some limit to be set (excludes case of no limits) */
type SomeLimit = AnyLimit & SomePropertyFrom<AnyLimit>;

/** Require a type's properties to be either fully present, or fully absent */
type AllOrNothing<T> =
  | T
  | {
      [k in keyof Required<T>]?: never;
    };

/** Require at least one assigned property from T */
type SomePropertyFrom<T> = { [K in keyof T]: Pick<Required<T>, K> }[keyof T];

相关问题