TypeScript の条件型に関する詳細な読書と実践記録

TypeScript の条件型に関する詳細な読書と実践記録

ほとんどのプログラムでは、入力に基づいて決定を下す必要があります。 TypeScript も例外ではなく、条件型を使用して入力型と出力型の関係を記述できます。

条件判断に使用される

条件判断を表現するために extends を使用する場合、次のルールをまとめることができます。

extends の両側の型が同じである場合、次の例に示すように、extends は意味的には === として理解できます。

type result1 = 'a' extends 'abc' ? true : false // false
type result2 = 123 extends 1 ? true : false // false

extends の右側の型に extends の左側の型が含まれている場合 (つまり、狭い型が広い型を拡張している場合)、結果は true になります。それ以外の場合は false になります。次の例を参照してください。

type result3 = string extends string | number ? true : false // true

オブジェクトに extends を適用すると、オブジェクトに指定されるキーが増えるほど、その型定義の範囲は狭くなります。次の例を参照してください。

type result4 = { a: true, b: false } extends { a: true } ? true : false // true

ジェネリック型での条件型の使用

次のデモ型定義を検討してください。

型 Demo<T, U> = T は U を拡張しますか? 決して: T

条件判断で使用される extends と組み合わせると、 'a' | 'b' | 'c' extends 'a' は false であることがわかります。したがって、 Demo<'a' | 'b' | 'c', 'a'> の結果は 'a' | 'b' | 'c'? になります。
公式ウェブサイトを確認してください。そこには次のように記載されています。

条件付き型がジェネリック型に作用する場合、ユニオン型が指定されると分散型になります。

つまり、条件型がジェネリック型に作用する場合、ユニオン型が分割されて使用されます。つまり、Demo<'a' | 'b' | 'c', 'a'> は、'a' extends 'a'、'b' extends 'a'、'c' extends 'a' に分割されます。疑似コードでは次のようになります:

関数Demo(T, U) {
  T.map(val => { を返す
    if (val !== U) valを返す
    'never' を返す
  })
}

Demo(['a', 'b', 'c'], 'a') // ['never', 'b', 'c']

さらに、never 型の定義によれば、never 型はすべての型に代入可能ですが、never にはどの型も代入できません (never 自身を除く)。つまり、 | 'b' | 'c' は 'b' | 'c' と同等ではありません。

したがって、Demo<'a' | 'b' | 'c', 'a'> の結果は 'a' | 'b' | 'c' ではなく 'b' | 'c' になります。

ツールタイプ

注意深い読者は、Demo 型の宣言プロセスが実際には TypeScript によって公式に提供されるツール型の Exclude<Type, ExcludedUnion> の実装原理であり、Type 型から共用体型 ExcludedUnion を除外するために使用されることに気付いたかもしれません。

type T = Demo<'a' | 'b' | 'c', 'a'> // T: 'b' | 'c'

デモ型定義に基づいて、公式ツール型にOmit<Type, Keys>をさらに実装することができます。これは、オブジェクトTypeを削除するために使用されます。
キー タイプを満たす属性値。

型 Omit<Type, Keys> = {
  [P in Demo<keyof Type, Keys>]: Type<P>
}

インターフェースTodo {
  タイトル: 文字列;
  説明: 文字列;
  完了: ブール値;
}

type T = Omit<Todo, 'description'> // T: { title: string; complete: boolean }

脱出ポッド

Demo<'a' | 'b' | 'c', 'a'> の結果を 'a' | 'b' | 'c' にしたい場合、それは可能ですか? 公式 Web サイトの説明によると:

通常、分散性が望ましい動作です。その動作を回避するには、extends キーワードの両側を角括弧で囲みます。

ジェネリック内のすべての型を反復処理したくない場合は、ジェネリックを角括弧で囲んで、ジェネリックを使用する部分全体を示すことができます。
型 Demo<T, U> = [T] は [U] を拡張しますか? 決して: T

型 Demo<T, U> = [T] は [U] を拡張しますか? 決して: T

// 結果の型は 'a' | 'b' | 'c' です
タイプ結果 = Demo<'a' | 'b' | 'c', 'a'>

矢印関数で条件型を使用する

矢印関数で三項式を使用する場合、括弧で囲まないと、左から右に読む習慣により関数のコンテンツ領域がわかりにくくなります。たとえば、次のコードでは、 x は関数型ですか、それともブール型ですか?

// 意図が明確ではありません。
var x = a => 1 ? 真: 偽

eslint ルール no-confusing-arrow では、以下が推奨されます。

var x = a => (1 ? 真: 偽)

TypeScript の型定義では、矢印関数で extends が使用されている場合も同様です。左から右に読む習慣があるため、型コードの実行順序について読者が混乱する可能性があります。

型 Curry<P extends any[], R> =
  (引数: Head<P>) => HasTail<P> は true を拡張しますか? Curry<Tail<P>, R> : R

したがって、矢印関数で extends を使用する場合は括弧を追加することをお勧めします。これはコードレビューに非常に役立ちます。

型 Curry<P extends any[], R> =
  (引数: Head<P>) => (HasTail<P> は true を拡張しますか? Curry<Tail<P>, R> : R)

型推論による条件型の使用

TypeScript では、型推論構文は通常、extends と組み合わせて使用​​されます。型を自動的に推論するという目的を達成するために使用します。たとえば、関数 Type の戻り値の型を返すために使用されるツール タイプ ReturnType<Type> を実装するために使用できます。
type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never

type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never

MyReturnType<() => 文字列> // 文字列
MyReturnType<() => Promise<boolean> // Promise<boolean>

extends と型推論を組み合わせると、配列関連の Pop<T>、Shift<T>、および Reverse<T> ツール型も実装できます。

ポップ<T>:

type Pop<T extends any[]> = T extends [...infer ExceptLast, any] ? ExceptLast : never

タイプ T = Pop<[3, 2, 1]> // T: [3, 2]

Shift<T>:

type Shift<T extends any[]> = T extends [infer _, ...infer O] ? O : なし

タイプ T = Shift<[3, 2, 1]> // T: [2, 1]

逆順<T>

型 Reverse<T> = T は [F を推論し、...Others を推論] を拡張します。
  ? [...逆<その他>, F]
  : []

type T = Reverse<['a', 'b']> // T: ['b', 'a']

条件型を使用して、2つの型が完全に等しいかどうかを判断します。

条件型を使用して、型 A と型 B が完全に等しいかどうかを判断することもできます。現在、コミュニティには 2 つの主な解決策があります。

解決策 1: 問題を参照してください。

エクスポート型Equal1<T, S> =
  [T]は[S]を拡張しますか?(
    [S] は [T] を拡張しますか? 真: 偽
  ) : 間違い

現時点でこのソリューションの唯一の欠点は、どのタイプも他のタイプと同等であると判断されることです。

type T = Equal1<{x:any}, {x:number}> // T: true

解決策 2: 問題を参照してください。

エクスポート型Equal2<X, Y> =
  (<T>() => TはXを拡張しますか? 1:2)は拡張します
  (<U>() => U は Y を拡張しますか? 1 : 2) ? 真: 偽

このソリューションの唯一の欠点は、交差タイプの処理にいくつかの欠陥があることです。

type T = Equal2<{x:1} & {y:2}, {x:1, y:2}> // 偽

型が等しいかどうかを判断する上記の 2 つの方法についてはさまざまな意見があり、著者は議論を刺激するためにいくつかのアイデアを提示しているだけです。

要約する

これで、TypeScript の条件型を集中的に読み、実践するこの記事は終了です。TypeScript の条件型に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • TypeScript 列挙の基本と例
  • Typescript+React でモバイルと PC でシンプルなドラッグ アンド ドロップ効果を実現
  • オブジェクトのプロパティを反復処理する際の TypeScript の問題

<<:  IDEA が MySQL データベースに接続できない問題の 6 つの解決策

>>:  LinuxにNginxをインストールする詳細な手順

推薦する

React でインデックスをキーとして使用することが推奨されないのはなぜですか?

1. 古い仮想DOMと新しい仮想DOMを比較し、まずキーが同じかどうかを確認します。 2. 引き続...

Linux システム AutoFs 自動マウント サービスのインストールと構成

目次序文1. サービスプログラムをインストールする2. メイン設定ファイルを書く3. サブ構成ファイ...

npmとcnpmを混在させる際の落とし穴の詳細な説明

目次原因理由NPM の紹介: CNPM の紹介:より良い方法方法の改善npm と cnpm を一緒に...

vue+springbootでログイン認証コードを実現

この記事では、ログイン認証コードを実装するためのvue+springbootの具体的なコードを例とし...

Docker+K8S クラスタ環境構築と分散アプリケーション展開

1. Dockerをインストールする yumでdockerをインストール #サービスを開始する sy...

Dockerコンテナのエクスポートとインポートの例

目次DockerコンテナのエクスポートDockerコンテナのインポ​​ートこの記事では主に、コンテナ...

知っておくべき 25 の Vue のヒント

目次1. プロパティを型リストに制限する2. デフォルトのコンテンツと拡張ポイント3. ネストされた...

カーソル ループを使用して、MySQL ストアド プロシージャで一時テーブルを読み取る

カーソルカーソルは、結果セット内のデータを表示または処理するために使用される方法です。カーソルを使用...

react-virtualized を使用して、動的な高さを持つ画像の長いリストを実装する

目次開発中に発生した問題解決具体的な実装実績まとめバーチャルリストは、スクロールコンテナ要素の表示領...

DD DT DLタグの使用例

通常は <ul><li> タグを使用しますが、dd タグと dt タグも便利...

Vue の詳細な入門ノート

目次1. はじめに2. 初期ビュー(I) Vueの概念を理解する(II) MVVMアーキテクチャ(I...

JavaScript を使用した数独の完全な実装プロセス

目次序文数独の解き方最初のボックスに記入してください2番目のボックスに記入してください3番目のボック...

MySQLのインデックス

序文早速本題に入りましょう。これからお話しするのは次のマインドマップです。まずは印象をつかんでくださ...

大きなオフセットによる MySQL 制限ページングが遅い理由と最適化ソリューション

MySQL では通常、limit を使用してページ上のページング機能を完了しますが、データ量が大きな...

Dockerイメージストレージoverlayfsの使用

1. 概要Docker のイメージはレイヤーで設計されています。各レイヤーは「レイヤー」と呼ばれます...