本日の勉強会で、TypeScriptのこのPRをみてinferとは何かを調べたので備忘録として。

結論

  • 型定義ファイルを作るときに利用する。
  • 依存している型を三項演算子の値として利用することができようにするためのもの。(@todo: この説明の正確性は要検討。)

JavaScriptで次の様なコードが実装されているとします。

簡単な、クイズを行うクラスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Questioner
{
constructor(question)
{
this.question = question;
}

assert(answer)
{
return (this.question.expected === answer);
}
}

export default class QuestionerFactory
{
static create(question)
{
return new Questioner(question);
}
}

クライアントコードはこんな感じです。

1
2
3
4
5
6
const questioner = QuestionerFactory.create({
detail: '私のこと0〜100で表すとしたらどれくらい好き?',
expected: '1と0の間の距離くらいだよ',
});

console.log(questioner.assert(100));

ちょっと質問内容が難しすぎます。
Questionerを利用する側では、せめてassertで引数の型ぐらい教えて欲しいものです。

TypeScriptによる型

TypeScriptでは型がありますので、型定義ファイルを作成することで
assertメソッドの引数の型を絞り込むことができます。

1
2
3
4
5
6
7
8
9
10
declare namespace QuestionerFactory {

function create<T>(question: T): Questioner<T>;

type Expected<Y> = Y extends { expected: infer Z } ? Z : never;

interface Questioner<X> {
assert(answer: Expected<X>): boolean;
}
}

この様にassertメソッドの引数の型がstringであるということがわかる様になります。

解説

ジェネリクス

1
function create<T>(question: T): Questioner<T>;

ここは、ジェネリクスです。
QuestionerFactory.createメソッドの引数TをQuestionerのXとして定義しています。
つまり、次の様なコードを書いたときの返り値はQuestioner<string>です。

1
2
3
4
QuestionerFactory.create<string>('sample');  // returns Questioner<string>

// 次の様に書いても型推論で、Tにstringは入るのでcreate<string>('sample')と同じ
QuestionerFactory.create('sample'); // returns Questioner<string>

ジェネリクス

型エイリアス

1
type Expected<Y> = Y extends { expected: infer Z } ? Z : never;

typeは型エイリアスです。

1
2
3
type hoge = string|number;

function fuga(value: hoge);

このように書くことで、fuga関数のvalueにはstringかnumberを入れる様に型定義することができます。

型エイリアス

ConditionalTypes

1
type Expected<Y> = Y extends { expected: infer Z } ? Z : never;

型を条件分岐して定義することができます。
例えば、次の例では、Ystringが拡張された型が定義された場合はnubmerになり、それ以外の場合はneverになります。

1
type Sample<Y> = Y extends string ? number : never;

三項演算子で分岐を定義しています。
次の様な、三項演算子のネストして記述することも可能な様です。

1
2
3
type Sample<Y> = Y extends string ? number :
Y extends number ? string:
never;

このコードは次のフローになります。

ConditionalTypes

Type inference in ConditionalTypes

1
type Expected<Y> = Y extends { expected: infer Z } ? Z : never;

inferでは定義したYが依存している型をZとして利用することができます。
次の様なフローになります。

この様に、依存している型を三項演算子の値として利用することができます。

Type inference in ConditionalTypes

まとめ

1
2
3
4
5
6
7
8
9
10
declare namespace QuestionerFactory {

function create<T>(question: T): Questioner<T>;

type Expected<Y> = Y extends { expected: infer Z } ? Z : never;

interface Questioner<X> {
assert(answer: Expected<X>): boolean;
}
}

この型定義は次の様なフローになります。

このため、assertメソッドの引数の型がZであるということがわかる様になります。

この記事のSinonのStubについて説明している例が、実例として非常にわかりやすかったです。

https://qiita.com/Quramy/items/b45711789605ef9f96de#%E4%BE%8B2-sinonjs%E3%81%AEstub