時間が経つにつれて、この資料の著者は、コンパイル段階で検出されたエラーをバイパスするたびに、プログラムの実行中に爆発する可能性のある時限爆弾をコードに入れることに気付きました。 単純な構造を使用してエラーに「苦労」するたびに、何時間ものハードデバッグでその費用を支払う必要がありました。

最後に、彼はそうしないほうが良いという結論に達しました。 彼はコンパイラと友達になり、彼のヒントに注意を払い始めました。 コンパイラーは、コード内の問題を検出し、実際の害を引き起こす可能性があるずっと前に報告します。 この記事の著者は、自分自身を開発者と見なしており、コンパイラーが自分から自分を保護しているため、コンパイラーが彼の親友であることに気付きました。 アルバス・ダンブルドアの言葉を思い出せないのは、「敵に立ち向かうには多くの勇気が必要ですが、友達に立ち向かうにはそれ以上の勇気が必要です」。
コンパイラがどれほど優れていても、喜ばせるのは必ずしも簡単ではありません。 タイプ
any
の使用を避けること
any
非常に難しい
any
あります。 そして、時にはそれが何らかの問題に対する唯一の合理的な解決策であると思われ
any
。
この資料では、2つの状況に焦点を当てています。 それらの中で型の使用を避けることにより、コードの型安全性を確保し、その再利用の可能性を開き、直感的にすることができます。
ジェネリック
学校のデータベースに取り組んでいるとします。 非常に便利なヘルパー関数
getBy
。 生徒を名前で表すオブジェクトを取得するには、
getBy(model, "name", "Harry")
という形式のコマンドを使用できます。 このメカニズムの実装を見てみましょう(ここでは、コードを複雑にしないために、データベースは通常の配列で表されています)。
type Student = { name: string; age: number; hasScar: boolean; }; const students: Student[] = [ { name: "Harry", age: 17, hasScar: true }, { name: "Ron", age: 17, hasScar: false }, { name: "Hermione", age: 16, hasScar: false } ]; function getBy(model, prop, value) { return model.filter(item => item[prop] === value)[0] }
ご覧のとおり、優れた関数がありますが、型注釈は使用されません。また、それらが存在しないことは、そのような関数を型セーフと呼ぶことができないことも意味します。 修正してください。
function getBy(model: Student[], prop: string, value): Student | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "name", "Hermione") // result: Student
したがって、私たちの機能はすでにずっと良く見えます。 コンパイラーは、期待される結果のタイプを認識します。これは後で便利になります。 ただし、型の安全な作業を実現するために、関数を再利用する可能性を犠牲にしました。 他のエンティティを取得するために使用する必要がある場合はどうなりますか? この機能を改善できないということはあり得ません。 そして、本当にそうです。
TypeScriptでは、他の強く型付けされた言語と同様に、ジェネリックを使用できます。ジェネリックは、「ジェネリック型」、「ユニバーサル型」、「ジェネラリゼーション」とも呼ばれます。
ジェネリックは通常の変数に似ていますが、値の代わりに型定義が含まれています。
Student
型の代わりに汎用型
T
使用するように、関数のコードを書き直します
T
function getBy<T>(model: T[], prop: string, value): T | null { return model.filter(item => item[prop] === value)[0] } const result = getBy<Student>(students, "name", "Hermione") // result: Student
美人! 現在、この関数は再利用に最適ですが、型の安全性は依然として私たちの側にあります。 ジェネリック
T
上記のコードスニペットの最後の行で、
Student
タイプが明示的に設定されていることに注意してください。 これは、例をできるだけ明確にするために行われますが、実際、コンパイラは必要な型を独立して導出できるため、次の例ではそのような型の改良は行いません。
これで、再利用に適した信頼できるヘルパー関数ができました。 ただし、それでも改善できます。 2番目のパラメーターを入力するときにエラーが発生し、
"name"
代わりに
"naem"
が表示されたらどうなるでしょうか。 この関数は、探している生徒がデータベースにいないかのように動作し、最も不快なことに、エラーを生成しません。 これにより、長期のデバッグが発生する可能性があります。
このようなエラーを防ぐために、別のユニバーサルタイプ
P
を導入します
P
さらに、
P
がタイプ
T
キーであることが必要です。したがって、ここで
Student
使用される場合、
P
は文字列
"name"
、
"age"
または
"hasScar"
である必要があります。 方法は次のとおりです。
function getBy<T, P extends keyof T>(model: T[], prop: P, value): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "naem", "Hermione") // Error: Argument of type '"naem"' is not assignable to parameter of type '"name" | "age" | "hasScar"'.
ジェネリックと
keyof
を使用することは非常に強力なトリックです。 TypeScriptをサポートするIDEでプログラムを作成する場合、引数を入力することにより、オートコンプリート機能を利用できます。これは非常に便利です。
ただし、
getBy
関数の作業はまだ完了していません。 彼女には3番目の引数があり、そのタイプはまだ設定されていません。 これは私たちにはまったく適していません。 今までは、2番目の引数として渡すものに依存するため、どのタイプであるかを事前に知ることができませんでした。 しかし、今ではタイプ
P
があるので、3番目の引数のタイプを動的に推測できます。 3番目の引数のタイプは、最終的に
T[P]
ます。 その結果、
T
が
Student
で、
P
が
"age"
場合、
T[P]
は型
number
ます。
function getBy<T, P extends keyof T>(model: T[], prop: P, value: T[P]): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "age", "17") // Error: Argument of type '"17"' is not assignable to parameter of type 'number'. const anotherResult = getBy(students, "hasScar", "true") // Error: Argument of type '"true"' is not assignable to parameter of type 'boolean'. const yetAnotherResult = getBy(students, "name", "Harry") //
TypeScriptでジェネリックを使用する方法について完全に理解できたと思いますが、ここで説明したコードを試して、すべてを十分に学習する場合は、 こちらをご覧ください 。
既存の型を拡張する
コードを変更できないインターフェイスにデータや機能を追加する必要がある場合があります。 標準オブジェクトを変更する必要があるかもしれません。たとえば、
window
オブジェクトにプロパティを追加したり、
Express
などの外部ライブラリの動作を拡張したりします。 また、どちらの場合も、作業対象のオブジェクトに直接影響を与えることはできません。
既に知っている
getBy
関数を
Array
プロトタイプに追加して、同様の問題の解決策を見ていきます。 これにより、この関数を使用して、より正確な構文構造を構築できます。 現時点では、標準オブジェクトを展開することが良いか悪いかについては話していません。なぜなら、私たちの主な目標は検討中のアプローチを研究することだからです。
Array
プロトタイプに関数を追加しようとすると、コンパイラはこれをあまり好まなくなります。
Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; // Error: Property 'getBy' does not exist on type 'any[]'. const bestie = students.getBy("name", "Ron"); // Error: Property 'getBy' does not exist on type 'Student[]'. const potionsTeacher = (teachers as any).getBy("subject", "Potions") // ... ?
as any
コンストラクトを定期的に使用してコンパイラーを安心させようとすると、達成したすべてが無効になります。 コンパイラーは沈黙しますが、型の安全な作業を忘れることができます。
Array
型を拡張することをお勧めしますが、これを行う前に、コードに同じ型の2つのインターフェイスが存在する場合にTypeScriptが状況を処理する方法について説明しましょう。 ここでは、アクションの簡単なスキームが適用されます。 可能であれば、広告は結合されます。 それらを結合できない場合、システムはエラーを出します。
したがって、このコードは機能します:
interface Wand { length: number } interface Wand { core: string } const myWand: Wand = { length: 11, core: "phoenix feather" } // !
そして、これはそうではありません:
interface Wand { length: number } interface Wand { length: string } // Error: Subsequent property declarations must have the same type. Property 'length' must be of type 'number', but here has type 'string'.
さて、これを理解すると、かなり単純なタスクに直面していることがわかります。 つまり、必要なのは、
Array<T>
インターフェイスを宣言し、
getBy
関数を追加することだけです。
interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; const bestie = students.getBy("name", "Ron"); // ! const potionsTeacher = (teachers as any).getBy("subject", "Potions") //
モジュールファイルに記述する可能性が高いコードのほとんどは、
Array
インターフェイスを変更するために、グローバルスコープにアクセスする必要があることに注意してください。 これを行うには、
declare global
内に型定義を配置します。 たとえば、次のように:
declare global { interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } }
外部ライブラリのインターフェースを拡張する場合、ほとんどの場合
namespace
このライブラリの
namespace
アクセスする必要があります。 以下に、
Express
ライブラリからの
Request
userId
フィールドを追加する方法の例を示します。
declare global { namespace Express { interface Request { userId: string; } } }
ここでこのセクションのコードを試すことができます 。
まとめ
この記事では、TypeScriptでジェネリックおよび型拡張機能を使用するための手法を検討しました。 今日学んだことが、信頼でき、理解しやすく、タイプセーフなコードを書くのに役立つことを願っています。
親愛なる読者! TypeScriptの型についてどう思いますか?
