Website-Suche

typescript - Wie man ein Partial-ähnliches erstellt, das erfordert, dass eine einzelne Eigenschaft gesetzt wird


Wir haben eine Struktur, die wie folgt aussieht:

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

Was fast dasselbe ist wie zu sagen

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

Außer dass das Partial es erlaubt, ein leeres Objekt zu übergeben, während der ursprüngliche Typ verlangt, dass eine der Eigenschaften übergeben wird.

Wie kann ich ein generisches wie Partial erstellen, das sich aber wie meine obige Struktur verhält?

Antwort #1

Sie suchen nach etwas, das einen Typ T nimmt und einen verwandten Typ erstellt, der mindestens eine Eigenschaft von T enthält. Es ist wie Partial<T>, schließt aber das leere Objekt aus.

Wenn ja, hier ist es:

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

Um es zu zerlegen, zunächst einmal:

  • AtLeastOne<T> ist Partial<T> geschnitten mit etwas.
  • U[keyof U] bedeutet, dass es die Vereinigung aller Eigenschaftswerte von U ist.
  • Und ich habe den Standardwert von U so definiert, dass es sich um einen gemappten Typ handelt, bei dem jede Eigenschaft von T auf Pick<T, K> abgebildet wird, ein Einzel-Eigenschaft-Typ für den Schlüssel K.

Zum Beispiel ist das Folgende äquivalent, weil es die Eigenschaft 'foo' aus dem ursprünglichen Typ „auswählt“:

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

das bedeutet, dass U[keyof U] in diesem Fall die Vereinigung aller möglichen Einzel-Eigenschaft-Typen von T ist. Lassen Sie uns Schritt für Schritt sehen, wie es auf den folgenden konkreten Typ wirkt:

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

type LinkRestSource = AtLeastOne<FullLinkRestSource>

was sich erweitert zu:

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

oder

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

oder

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

oder

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

oder

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

oder

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

oder

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

oder

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

was, denke ich, du willst.

Du kannst es ausprobieren:

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

Antwort #2

Es gibt eine andere Lösung, wenn Sie wissen, welche Eigenschaften Sie möchten.

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

Dies würde es Ihnen auch ermöglichen, mehrere Schlüssel eines Typs zu sperren, z. B.

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

Antwort #3

Eine einfachere Version dieser Lösung:

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

also wird die gesamte Implementierung

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

und hier ist der TS-Playground-Link, um es auszuprobieren

Verwandte Artikel