wir können... ??


5. Was funktioniert: das destrukturierte Tupel

type Provider = \\\"PROVIDER A\\\" | \\\"PROVIDER B\\\";type ProviderAOpts = { ... };type ProviderBOpts = { ... };function connect(  ...[provider, options]:    | [\\\"PROVIDER A\\\", ProviderAOpts]    | [\\\"PROVIDER B\\\", ProviderBOpts]) {  switch (provider) {    case \\\"PROVIDER A\\\":      // options is ProviderAOpts ✅    case \\\"PROVIDER B\\\":      // options is ProviderBOpts ✅    ...  }}
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅

Die Sache ist also, dass wir ein Tupel (Array) mit genau den Typen zerstören, die wir wollen.

Der einzige Nachteil: Wenn wir wählerisch sind und dem Tupel weitere Paare hinzufügen, können wir hier einen generischen Typ extrahieren:


6. Was funktioniert: verallgemeinerte Tupellösung

type Provider = \\\"PROVIDER A\\\" | \\\"PROVIDER B\\\";type ProviderAOpts = { ... };type ProviderBOpts = { ... };type ProviderOpts = {  \\\"PROVIDER A\\\": ProviderAOpts;  \\\"PROVIDER B\\\": ProviderBOpts;};// solves to // [\\\"PROVIDER A\\\", ProviderAOpts] | [\\\"PROVIDER B\\\", ProviderBOpts]type ConnectOptions = {  [K in keyof ProviderOpts]: [K, ProviderOpts[K]];}[keyof ProviderOpts]; function connect(...[provider, options]: ConnectOptions) {  switch (provider) {    case \\\"PROVIDER A\\\":      // options is ProviderAOpts ✅    case \\\"PROVIDER B\\\":      // options is ProviderBOpts ✅    ...  }}
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅

7. TL;DR. KOPIEREN EINFÜGEN, DANKE

type Provider = \\\"PROVIDER A\\\" | \\\"PROVIDER B\\\";type ProviderAOpts = { ... };type ProviderBOpts = { ... };type ProviderOpts = {  \\\"PROVIDER A\\\": ProviderAOpts;  \\\"PROVIDER B\\\": ProviderBOpts;};// aux type to extract the key and the options from ProviderOptstype KeyOpts = {  [K in keyof T]: [K, T[K]];}[keyof T];function connect(...[provider, options]: KeyOpts) {  switch (provider) {    case \\\"PROVIDER A\\\":      // options is ProviderAOpts ✅    case \\\"PROVIDER B\\\":      // options is ProviderBOpts ✅    ...  }}
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅

Danke an Mateusz und Lenz für die Hilfe?.

Danke fürs Lesen?.

","image":"http://www.luping.net/uploads/20241104/17307234156728be5752a02.jpg","datePublished":"2024-11-07T10:48:39+08:00","dateModified":"2024-11-07T10:48:39+08:00","author":{"@type":"Person","name":"luping.net","url":"https://www.luping.net/articlelist/0_1.html"}}
„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > Fortgeschrittene Ts: Abhängige Parameter, abgeleitete Gewerkschaften und eine gesunde Interaktion auf Twitter.

Fortgeschrittene Ts: Abhängige Parameter, abgeleitete Gewerkschaften und eine gesunde Interaktion auf Twitter.

Veröffentlicht am 07.11.2024
Durchsuche:787

Advanced Ts: Dependent parameters, inferred unions and a healthy interaction on Twitter.

Jedes Mal, wenn ich als Foo in TypeScript schreibe, spüre ich die Last der Niederlage.

Es gibt ein Szenario, in dem dieses Gefühl besonders intensiv ist: Wenn eine Funktion einen Parameter annimmt, der davon abhängt, welcher "Modus" aktiv ist.

Klarer mit Beispielcode:

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

function connect(provider: Provider, options: ProviderAOpts | ProviderBOpts)  {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts
    case "PROVIDER B":
      // options is ProviderBOpts
  }
}

(Ich habe versucht, realistischere Namen anstelle von foo, goo, dog und cat zu verwenden).

Wenn Sie einige Zeit mit TypeScript verbracht haben, könnten Sie vermuten, dass wir dies früher mit ProviderAOpts und ProviderBOpts gehandhabt haben.


Aber manchmal schlägt man mit der Faust auf den Tisch und behauptet: „Nicht mehr!“


1. Was nicht funktioniert

Das erste, was mir in diesen Fällen immer in den Sinn kommt, ist die Verwendung von Funktionsüberladung:

function connect(provider: "PROVIDER A", options: ProviderAOpts): void;
function connect(provider: "PROVIDER B", options: ProviderBOpts): void;

function connect(provider: Provider, options: ProviderAOpts | ProviderBOpts) {
  switch (provider) {
    case "PROVIDER A":
    // (options as ProviderAOpts) ❌
    case "PROVIDER B":
    // (options as ProviderBOpts) ❌
  }
}

Was nicht funktioniert. Die Funktionssignatur wird nicht korrekt abgeleitet. Der Optionsparameter ist immer ProviderAOpts | ProviderBOpts. die sich zur gemeinsamen Union auflösen wird.

Ts verknüpft beide Parameter nicht korrekt.


2. Was funktioniert, aber nicht die Verknüpfung der Parameter ist

Das nächste Tool, das ich versuche, sind Typprädikate:

type ConnectOptions = ProviderAOpts | ProviderBOpts;

function isAOptions(options: ConnectOptions): options is ProviderAOpts {
  return (options as ProviderAOpts).$$$ !== undefined;
}

function isBOptions(options: ConnectOptions): options is ProviderBOpts {
  return (options as ProviderBOpts).$$$ !== undefined;
}

function connect(provider: Provider, options: ConnectOptions) {
  switch (provider) {
    case "PROVIDER A":
      if (isAOptions(options)) {
        ...
      }
    case "PROVIDER B":
      if (isBOptions(options)) {
        ...
      }
  }
  ...
}

Aber ehrlich gesagt haben wir nichts gelöst. Wir haben das einfach unter den Teppich geschoben? Zusätzliche ifs und eingeführt, wir verknüpfen die Parameter immer noch nicht.


3. Was nicht funktioniert und mich zum Weinen bringt

Generika. Ich habe versucht, Generika zu verwenden, um die Parameter zu verknüpfen. Funktioniert nicht:

function connect(
  provider: T,
  options: T extends "PROVIDER A" ? ProviderAOpts : ProviderBOpts
) {
  switch (provider) {
    case "PROVIDER A":
    // (options as ProviderAOpts) ❌
    case "PROVIDER B":
    // (options as ProviderBOpts) ❌
  }
}

Ich habe mich so sehr bemüht und bin so weit gekommen
Aber am Ende spielt es keine Rolle
Ich musste fallen, um alles zu verlieren
Aber am Ende spielt es keine Rolle
?‍?


4. Was funktioniert, zwingt uns aber dazu, die Funktionssignatur zu ändern?

Das Ändern der Opt-Parameter durch Hinzufügen des Anbietertyps reicht aus:

type Provider = "PROVIDER A" | "PROVIDER B";

type ProviderOptsBase = {
  provider: Provider;
}

type ProviderAOpts = ProviderOptsBase & {
  provider: "PROVIDER A";
  ...;
};

type ProviderBOpts = ProviderOptsBase & {
  provider: "PROVIDER B";
  ...;
};

function connect(options: ConnectOptions) {
  switch (options.provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
  }
}

Dies ist die häufigste Lösung, es ist jedoch nicht immer möglich, die Funktionssignatur zu ändern. Oder vielleicht wollen Sie es einfach nicht. Grundsatzfrage ?.


Twitter zur Rettung

Danke an Mateusz Burzyński (@AndaristRake) und Lenz Weber (@phry)

wir können... ??


5. Was funktioniert: das destrukturierte Tupel

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

function connect(
  ...[provider, options]:
    | ["PROVIDER A", ProviderAOpts]
    | ["PROVIDER B", ProviderBOpts]
) {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
    ...
  }
}
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

Die Sache ist also, dass wir ein Tupel (Array) mit genau den Typen zerstören, die wir wollen.

Der einzige Nachteil: Wenn wir wählerisch sind und dem Tupel weitere Paare hinzufügen, können wir hier einen generischen Typ extrahieren:


6. Was funktioniert: verallgemeinerte Tupellösung

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

type ProviderOpts = {
  "PROVIDER A": ProviderAOpts;
  "PROVIDER B": ProviderBOpts;
};

// solves to 
// ["PROVIDER A", ProviderAOpts] | ["PROVIDER B", ProviderBOpts]
type ConnectOptions = {
  [K in keyof ProviderOpts]: [K, ProviderOpts[K]];
}[keyof ProviderOpts]; 

function connect(...[provider, options]: ConnectOptions) {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
    ...
  }
}
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

7. TL;DR. KOPIEREN EINFÜGEN, DANKE

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

type ProviderOpts = {
  "PROVIDER A": ProviderAOpts;
  "PROVIDER B": ProviderBOpts;
};

// aux type to extract the key and the options from ProviderOpts
type KeyOpts = {
  [K in keyof T]: [K, T[K]];
}[keyof T];


function connect(...[provider, options]: KeyOpts) {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
    ...
  }
}
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

Danke an Mateusz und Lenz für die Hilfe?.

Danke fürs Lesen?.

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/manuartero/advanced-ts-dependent-parameters-inferred-unions-and-a-healthy-interaction-on-twitter-13bc?1 Wenn es einen Verstoß gibt, bitte Kontaktieren Sie Study_golang@163 .comdelete
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3