関数はfunction
から始まる書き方のものとアロー関数どちらも JavaScript のものと同じように書けます。違いは型を追加するだけです。
plusOne 関数
ある数値に1
足すだけのplusOne
関数を定義してみます。function
のほうで書くと以下、
function plusOne(num: number) {
return num + 1;
}
// 暗黙的に戻り値は`number`
アロー関数で書くと以下のようになります。
const plusOne = (num: number) => {
return num + 1;
};
引数で変数名: 型, 変数名: 型
の形で定義していきます。上記のような関数は単純でだいたい何をしているか分かるので TypeScript が暗黙的に設定した戻り値の型(number
)で済ますしても大丈夫そうですが、もし複雑で他人が見た時にすぐに判断できないような内容になっていると思ったら戻り値も明確にしておくといいと思います。
戻り値は(...引数): 型
のように引数の括弧の後に型を書きます。上記で書いた関数にそれぞれ戻り値情報を足すと以下になります。
function plusOne(num: number): number {
return num + 1;
}
const plusOne = (num: number): number => {
return num + 1;
};
型を先に作りたい場合があるかもしれません。そういう場合はinterface
やtype
で以下のように定義できます。
interface IPlugOne {
(num: number): number
}
type TPlusOne1 = {
(num: numbeer): number
}
type TPlusOne2 = (num: number) => number
これらと共にplusOne
を定義するには以下のようになります。
const plugOne: IPlugOne = (num): number => num + 1
好きなプロパティを持つ関数
時々関数として実行してるのに、プロパティアクセスも行ってるものを見かけます。
foo(arg1, arg2);
console.log(foo.bar);
foo.baz = 123;
これは TypeScript でもお簡単に実装できます。関数に好きなプロパティを追加できるようになってる為です。
function foo() {}
foo.bar = 123
// またはアローで
const foo = () => {}
foo.bar = 123
上記のfoo
は型で表すと以下のようなインターフェースになります。
interface IFoo {
(): void
bar: number
}
type TFoo = {
(): void
bar: number
}
const foo: IFoo = () => {}
foo.bar = 123
逆に型を先に定義すると、生やせるプロパティに制約を掛けるということになります。
interface IFoo {
(): void
// baz に変更
baz: number
}
const foo: IFoo = () => {}
// bar なんて知らないのでアクセスしたり値を入れれない
foo.bar = 123
ジェネリック 関数
引数がobject
のように単純ではないけれど、ある程度制約を持たせたい(foo
というプロパティを持っている構造体を受け取ってその構造体を返したい)ような場合に便利です。ジェネリックは関数名のあとに<T, U>
のような感じで定義できるもので、これにより型定義を柔軟にすることができます。
interface A {
value: number;
}
interface B extends A {}
interface C extends A {}
function plusOne(obj: T): T {
obj.value = obj.value + 1;
return obj;
};
const b: B = {value: 1};
const c: C = {value : 2}
plusOne(b); // {value: 2};
plusOne(c); // {value: 3};
extends
を使うことでT
はA
を持っている、またはA
に属していなければいけないというような制約付けができます。つまり、plusOne({foo: 1})
というなものはvalue
プロパティがないので型エラーになります。
また、上記では動的にT
の型を設定しています。
plusOne(b)
でb
が渡ることで(obj: T)
が(obj: B)
ということになります。(obj: B)
からfunction plusOne<B>(obj: B)
とジェネリクスが埋まりつまり、
function plusOne<B>(obj: B): B
のように解決されます。もちろんジェネリクスは自分で設定することもできます。その場合は関数呼び出しの名前のあとにジェネリクスを設定します。
ただし、このようにジェネリクスを自分で設定できるのはfunction
から書く記法だけです。アロー関数の場合自分でジェネリクスを設定するにはキャストが必要ですが、以下のようにジェネリクスなアロー関数を定義することはできます。
const plusOne = (obj: T): T {
obj.value = obj.value + 1;
return obj;
};
動的解決でプロパティを絞り込む
動的の利点の1つにどのプロパティ値を受け取ったか分かるというのがあると思います。
interface A {
str: string;
num: number;
}
function getProp(obj: T, prop: P): T[P] {
return obj[prop];
};
const a: A = {
str: 'a',
num: 1,
}
getProp(a, 'str');
getProp(a, 'num');
それぞれ戻り値がgetProp(a, 'str')
とした時はstring
、getProp(a, 'num')
とした時はnumber
になります。これはP extends keyof T
(つまりここではP extends 'str' | 'num'
)のP
に実際に指定されたプロパティの値を設定してくれることで、T[P]
がT['str']
,T['num']
と解決してくれるためです。
例えばこれは、getProp<A, keyof A>(a, 'num')
と使ったらどうなるでしょう。これはP
にすべてのプロパティが来る可能性がでてくるので、オブジェクトの値の型の Union 、今回であればstring | number
という戻り型になります。