1. ホーム
  2. typescript

[解決済み] Typescriptで<T>は何を意味するのですか?

2022-12-11 08:20:39

質問

export declare function createEntityAdapter<T>(options?: {
    selectId?: IdSelector<T>;
    sortComparer?: false | Comparer<T>;
}): EntityAdapter<T>;

誰かこの意味を説明してくれませんか? 私は知っている <> が型アサーションであることは知っていますが、何のために 'T' がわからない。 また、誰かがこの関数が何をしているのか説明してくれると助かります。

どのように解決するのですか?

誰か私に説明してください <T> は何を意味するのでしょうか?

それは、タイプスクリプト ジェネリック の宣言になります。

抜粋です。

ソフトウェア工学の主要な部分は、明確に定義された一貫性のあるAPIを持つだけでなく、再利用可能なコンポーネントを構築することです。今日のデータと明日のデータを扱うことができるコンポーネントは、大規模なソフトウェアシステムを構築するための最も柔軟な能力を与えてくれます。

C# や Java のような言語では、再利用可能なコンポーネントを作成するためのツールボックスの主要なツールの 1 つがジェネリックスです。これにより、ユーザーはこれらのコンポーネントを消費して、独自の型を使用することができます。

とおっしゃっています。

<ブロッククオート

T'が何であるかは知らない。

'T' の代わりに実行時に宣言される型になります。 コンパイル の時に宣言された型になります。 そのため T 変数は宣言されていない任意の変数である可能性があります(参考文献は見つかりませんでしたが、変数名に使用できる任意の有効な文字のセットを仮定します)。 同様に c# では、もし型 T が値の型ではなく、より複雑な型(クラス)やインターフェイスを表している場合、以下のように命名/宣言される可能性があります。 TVehicle または TAnimal を使用することで、将来のプログラマーのために有効な型を示すことができます(また、単に T は直感的でないからです)。私は TSomething の方が好きです。なぜなら、大文字のTは一般的な型を意味することが分かっているからです。 WSometing あるいは ASomething も有効ですが、私はこれを好みません。 (マイクロソフトのAPIは、ほとんど常に TContext または TEntity など)。

また、この関数が何をしているのか、どなたか説明していただけると助かります。

さて、この関数は していません。 何かをしているわけではありません。 これはむしろ、複数の実行時型値を持つことができる関数の型を宣言しているのです。 それを説明する代わりに、上のリンクから直接抜粋したものを載せます。

function identity<T>(arg: T): T {
  return arg;
}

のように使うことができる。

// type of output will be 'string'
let output = identity<string>("myString");  

または

// type of output will be 'string', the compiler will figure out `T`
// based on the value passed in
let output = identity("myString");  

または

// type of output will be 'number'
let output = identity(8675309);  

という疑問が湧くかもしれません。

なぜジェネリックを使うのか

Javascriptには配列がありますが、配列から値を取り出すと、文字通り何でもありになってしまいます(typescript: any ). typescriptでは、このように宣言することで、Type safetyを得ることができます。

 // Array<T>
 let list: number[] = [1, 2, 3];
 // or 
 let list: Array<number> = [1, 2, 3];

これで配列の各値は型を持つようになりました。 Typescript はこの配列に文字列を入れようとすると、コンパイル時にエラーを投げます。 また、値を取り出す際には、型安全性とインテリセンスが得られます(エディタに依存します)。

class Person {
  FirstName: string;
}

let people: Array<Person> = [];
people.push({ FirstName: "John" } as Person);

let john = people.pop();  
// john is of type Person, the typescript compiler knows this
// because we've declared the people variable as an array of Person

console.log(john.FirstName);  

型付き汎用制約を宣言する。の非常に良い例です。 オープン-クローズの原則 .

オブジェクト指向プログラミングにおいて、オープン/クローズの原則は、quot;ソフトウェアの実体(クラス、モジュール、関数など)は、拡張に対してはオープンであるが、修正に対してはクローズであるべきである[1]、つまり、その実体は、そのソースコードを変更することなくその動作を拡張できるようにすることである。

次の例では、誰もが Human や Cheetah を拡張したり、独自の派生型を作成したりすることができ、Logger の機能は何の変更もなく動作し続けます。

interface IAnimal {
  LegCount: number;
}

class Cheetah 
  implements IAnimal {
  LegCount: number = 4;
}

class Human
  implements IAnimal {
  LegCount: number = 2;
}

public class Logger<TAnimal extends IAnimal> {
  public Log(animal: TAnimal) {
    console.log(animal.LegCount);
  }
}
 
var logger = new Logger();
var human = new Human();
logger.Log(human);      

作業例

前の例では 一般的な制約 を制限するために TAnimal 型のプログラマが Logger のインスタンスを,インターフェイスから派生した型に変換することができます. IAnimal . これによって、コンパイラは Logger クラスは常にその型がプロパティを持つことを想定しています。 LegCount .

なぜTypescriptのドキュメントでは、例えば<Identity>のようなもっと説明的なものを置かずに、<T>を置くのか、説明してください。私にとっては、何もないのと<T>は同じです。今、誰もが馬鹿のように <T> を使っているのでしょうか、それとも私が何かを見逃しているのでしょうか?

これらはすべて、以下における仮定になります。 私はtypescriptのジェネリックシステムを設計したチームもドキュメントを書いたチームも知らないのです。

ジェネリックの根源的なレベルでは T として 任意の可能な型 (typescriptと混同しないように any ). 意味 Array<T> インタフェース (言葉は悪いですが) であり、具体的な型を作成するときには、その型を T を宣言された型に置き換えます。

Array<number>

ということで インターフェース Array<T> よりも意味があるのは T ? わかりません。 私が知っているのは T は Type (数値、文字列など) でなければならないので T は単語の最初の文字であるため タイプ . と思います。 Array<Type> は本当に紛らわしく、また無効でさえあるかもしれません。 タイプ または タイプ が予約または制限されるようになりました(現在は type は特定の文脈で特別な意味を持つので、これも悪い選択です)ので、これらを避けることは良い選択です。 他の言語( C-シャープ , ジャバ ) も選択し T を使うので、言語間で切り替えて同じ用語を使えるのは有利です。

翻って、次のような場合はどうでしょうか。

Array<Identity>

何が Identity は何でしょうか? 他の開発者や将来の開発者がそれが何であるかを知るのに役立つ制約がないのです。 私には、明示的に実装しなければならない特定の型付けされた Array に見えます。つまり、一般的な型を選択するのは私次第ではないのです。

interface Foo1 {
  bars: Array<Identity>;
}

前の例では、私 (そしておそらくほとんどの開発者) は Identity が既存の型であり、それを変更することはできないと仮定しています。

interface Foo2<T> {
  bars: Array<T>;
}

とは Foo2 タイプを選択する必要があることは分かっています。

interface Foo3<Identity> {
  bars: Array<Identity>;
}

Foo3 は紛らわしいだけです。

interface Foo4<TIdentity> {
  bars: Array<TIdentity>;
}

では Foo4 で、型を選択しなければならないことに確信が持てましたが、まだ少し混乱しています。 TIdentity . 明らかに、型がより定義されているいくつかのコンテキストでは、それは意味をなさないでしょう。

編集する 2021年3月の時点で タイプスクリプト・ドキュメント の使用を非推奨とするために更新されました。 <T> の使用を非推奨とし、代わりに <Type> .

ということで、私は 個人的に は紛らわしいと思うので、次のようなものは避けることを強くお勧めします。 Type を避けることを強くお勧めします。

interface Lengthwise {
  length: number;
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length); 
  return arg;
}

型名に名前をつけない理由の第一は Type を使用しない最初の理由は、メソッド内または複数のメソッドであまりうまく読み取れないことです。 ロギング・メソッドが2行よりはるかに長く、突然、読んでいるものがすべて、単語 Type という単語がいたるところにあります。

interface Lengthwise {
  length: number;
}

interface Widthwise {
  width: number;
}

function loggingLength<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length);
  return arg;
}

function loggingWidth<Type extends Widthwise >(arg: Type): Type {
  console.log(arg.width);
  return arg;
}


以下の方が読みやすいと思います。

interface Lengthwise {
  length: number;
}

interface Widthwise {
  width: number;
}

function loggingLength<TLengthwise extends Lengthwise>(arg: TLengthwise): TLengthwise {
  console.log(arg.length);
  return arg;
}

function loggingWidth<TWidthwise extends Lengthwise>(arg: TWidthwise ): TWidthwise {
  console.log(arg.length);
  return arg;
}


次に、1つ以上のジェネリックを持つことは非常に可能であり、それは以下のことを無効にしています(明白な理由であることを望みます)。

interface Lengthwise {
  length: number;
}

interface Widthwise {
  width: number;
}

function loggingLength<Type extends Lengthwise, Type extends Widthwise>
(arg1: Type, arg2: Type) {
  console.log(arg1.length);
  console.log(arg2.width);
}


ドキュメントでは Type を使用していますが、私は Type を使わないことを強くお勧めします。