関数は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という戻り型になります。