ホームページ >ウェブフロントエンド >jsチュートリアル >高度な Ts: 依存パラメータ、推論された結合、Twitter での健全なインタラクション。

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

Barbara Streisand
Barbara Streisandオリジナル
2024-10-02 22:30:03669ブラウズ

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

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

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

いくつかのサンプルコードを使用するとより明確になります:

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

(フー、グー、犬、猫ではなく、より現実的な名前を使用しようとしました)。

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 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<T extends Provider>(
  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) に感謝します


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

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 ✅
    ...
  }
}
5. 機能するもの: 構造化されたタプル
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

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


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

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 ✅
    ...
  }
}
6. 機能するもの: 一般化されたタプル ソリューション
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

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<T> = {
  [K in keyof T]: [K, T[K]];
}[keyof T];


function connect(...[provider, options]: KeyOpts<ProviderOpts>) {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
    ...
  }
}
7.TL;DR。コピーペーストしてください、ありがとう
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

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

読んでいただきありがとうございます?. <script> // Detect dark theme var iframe = document.getElementById('tweet-1840828253684056557-683'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1840828253684056557&theme=dark" } </script> <script> // Detect dark theme var iframe = document.getElementById('tweet-1840346445334864141-950'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1840346445334864141&theme=dark" } </script>

以上が高度な Ts: 依存パラメータ、推論された結合、Twitter での健全なインタラクション。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。