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>istPartial<T>geschnitten mit etwas.U[keyof U]bedeutet, dass es die Vereinigung aller Eigenschaftswerte vonUist.- Und ich habe den Standardwert von
Uso definiert, dass es sich um einen gemappten Typ handelt, bei dem jede Eigenschaft vonTaufPick<T, K>abgebildet wird, ein Einzel-Eigenschaft-Typ für den SchlüsselK.
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
