typescript - Cara membuat tipe mirip Partial yang mewajibkan satu properti diatur


Kami memiliki sebuah struktur yang seperti berikut:

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

Yang hampir sama dengan mengatakan

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

Kecuali bahwa Partial akan memungkinkan sebuah objek kosong untuk dikirimkan sementara tipe awal mengharuskan salah satu properti untuk dikirimkan.

Bagaimana saya bisa membuat generic seperti Partial, tetapi yang berperilaku seperti struktur saya di atas?

Jawaban #1

Anda sedang mencari sesuatu yang mengambil sebuah tipe T dan menghasilkan tipe terkait yang berisi setidaknya satu properti dari T. Ini seperti Partial<T>, tetapi mengecualikan objek kosong.

Jika begitu, ini dia:

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

Untuk membedahnya, pertama-tama:

  • AtLeastOne<T> adalah Partial<T> yang dipotong dengan sesuatu.
  • U[keyof U] berarti bahwa itu adalah gabungan dari semua nilai properti dari U.
  • Dan saya telah mendefinisikan nilai default dari U sebagai tipe yang dipetakan di mana setiap properti dari T dipetakan ke Pick<T, K>, sebuah tipe dengan satu properti untuk kunci K.

Sebagai contoh, berikut ini setara karena ia "memilih" properti 'foo' dari tipe asli:

Pick<{foo: string, bar: number},'foo'>
// is equivalent to 
{foo: string}

yang berarti bahwa U[keyof U] dalam kasus ini adalah gabungan dari semua tipe properti tunggal yang mungkin dari T. Mari kita lihat langkah demi langkah bagaimana ini bekerja pada tipe konkret berikut:

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

type LinkRestSource = AtLeastOne<FullLinkRestSource>

yang berkembang menjadi:

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

atau

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

atau

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

atau

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

atau

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

atau

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

atau

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

atau

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

yang saya kira, itulah yang kamu inginkan.

Anda bisa mencobanya:

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

Jawaban #2

Ada solusi lain jika Anda tahu properti mana yang Anda inginkan.

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

Ini juga akan memungkinkan Anda untuk mengunci beberapa kunci dari satu jenis, misalnya.

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

Jawaban #3

Versi yang lebih sederhana dari solusi ini:

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

jadi seluruh implementasinya menjadi

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

dan ini link playground TS untuk mencobanya

Artikel terkait