How to create a Partial-like that requires a single property to be set

后端 未结 5 1199
春和景丽
春和景丽 2020-11-29 03:24

We have a structure that is like the following:

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


        
5条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-11-29 03:58

    I think I have a solution for you. You're looking for something that takes a type T and produces a related type which contains at least one property from T. That is, it's like Partial but excludes the empty object.

    If so, here it is:

    type AtLeastOne }> = Partial & U[keyof U]
    

    To dissect it: first of all, AtLeastOne is Partial intersected with something. U[keyof U] means that it's the union of all property values of U. And I've defined (the default value of) U to be a mapped type where each property of T is mapped to Pick, a single-property type for the key K. (For example, Pick<{foo: string, bar: number},'foo'> is equivalent to {foo: string}... it "picks" the 'foo' property from the original type.) Meaning that U[keyof U] in this case is the union of all possible single-property types from T.

    Hmm, that might be confusing. Let's see step-by-step how it operates on the following concrete type:

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

    That expands to

    type LinkRestSource = AtLeastOne
    }>
    

    or

    type LinkRestSource = AtLeastOne,
      rel: Pick,
      title: Pick
    }>
    

    or

    type LinkRestSource = AtLeastOne
    }>
    

    or

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

    or

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

    or

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

    or

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

    or

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

    which is, I think, what you want.

    You can test it out:

    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
    

    So, does that work for you? Good luck!

提交回复
热议问题