私たちは…に辿り着くことができます??


5. 機能するもの: 構造化されたタプル

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 ✅

つまり、必要な正確な型でタプル (配列) を構造化しているということです。

唯一の欠点は、タプルにさらにペアを追加することにこだわる場合です...ここでジェネリック型を抽出できます:


6. 機能するもの: 一般化されたタプル ソリューション

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。コピーペーストしてください、ありがとう

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 ✅

Mateusz と Lenz の協力に感謝します ?.

読んでいただきありがとうございます?.

","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"}}
「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > 高度な Ts: 依存パラメータ、推論された結合、Twitter での健全なインタラクション。

高度な Ts: 依存パラメータ、推論された結合、Twitter での健全なインタラクション。

2024 年 11 月 7 日に公開
ブラウズ:499

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

TypeScript で Foo として書くたびに、私は敗北の重さを感じます。

この感情が特に強くなるシナリオが 1 つあります。それは、どの 「モード」 がアクティブであるかに応じて関数がパラメーターを受け取る場合です。

コード例を使用するとよりわかりやすくなります:

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
  }
}

(foo、goo、dog、cat ではなく、より現実的な名前を使用しようとしました)。

TypeScript を少し使ったことがある人なら、これを ProviderAOpts や ProviderBOpts として処理していたのではないかと疑うかもしれません。


しかし、テーブルに拳を叩きつけて主張するときがあります:「もうだめ!」


1. うまくいかないこと

このような場合にいつも最初に思い浮かぶのは、関数のオーバーロード:
を使用することです。

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) ❌
  }
}

それはうまくいきません。関数のシグネチャが正しく推論されません。オプション パラメーターは常に ProviderAOpts | です。プロバイダーBOpts。それは共通の結合に解決されます。

Ts は両方のパラメータを正しくリンクしていません。


2. 機能するがパラメータがリンクされていないもの

次に試すツールは Type Predicates:
です

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)) {
        ...
      }
  }
  ...
}

しかし、正直に言うと、何も解決しませんでした。を敷物の下に移動しただけです?追加の if が導入されましたが、パラメータはまだリンクされていません。


3. うまくいかなくて泣いてしまうもの

ジェネリック。ジェネリックを使用してパラメータをリンクしようとしました。機能しません:

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) ❌
  }
}

頑張ってここまでたどり着きました
でも結局のところ、それは問題でもありません
すべてを失うには転ばなければならなかった
でも結局のところ、それは問題でもありません
?‍?


4. 機能するが関数シグネチャの変更が必要なもの

opts パラメータを変更してプロバイダー タイプを追加するとうまくいきます:

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 ✅
  }
}

これは最も一般的な解決策ですが、関数のシグネチャを変更できるとは限りません。あるいは、単にそうしたくないかもしれません。原則の問題 ?.


ツイッターが助けてくれる

Mateusz Burzyński (@AndaristRake) と Lenz Weber (@phry) に感謝します

私たちは…に辿り着くことができます??


5. 機能するもの: 構造化されたタプル

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 ✅

つまり、必要な正確な型でタプル (配列) を構造化しているということです。

唯一の欠点は、タプルにさらにペアを追加することにこだわる場合です...ここでジェネリック型を抽出できます:


6. 機能するもの: 一般化されたタプル ソリューション

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。コピーペーストしてください、ありがとう

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 ✅

Mateusz と Lenz の協力に感謝します ?.

読んでいただきありがとうございます?.

リリースステートメント この記事は次の場所に転載されています: https://dev.to/manuartero/advanced-ts-dependent-parameters-inferred-unions-and-a-healthy-interaction-on-twitter-13bc?1 侵害がある場合は、 Study_golang@163 .comdelete に連絡してください
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3