TypeScriptについての覚え書き
公式ページ
TypeScript コンパイラオプション
コンパイラオプション PathsとbaseUrlについて
TypeScriptの型注釈(Type Annotations)
オブジェクト型の書き方
const obj: {
foo: number;
bar: string;
} = {
foo: 123,
bar: "Hello, world!"
};
型エイリアス(type文で型に別名をつける)
type FooBarObj = {
foo: number;
bar: string;
};
const obj: FooBarObj = {
foo: 123,
bar: "Hello, world!"
};
interface宣言でオブジェクト型を宣言する
interface FooBarObj {
foo: number;
bar: string;
}
const obj: FooBarObj = {
foo: 0,
bar: "string"
};
ジェネリック型(型引数を持つ型の宣言)
型引数は、型を定義するときにパラメータをもたせることができる。
型引数は、type文(またはinterface宣言)で作成するときに宣言する。
型引数を宣言すると、その型引数は、その宣言の中(type文の=の右側)でだけ有効な型名として扱われる。
例1)
型引数Tを持つUser型を宣言
type User<T> = {
name: string;
child: T;
};
例2)
型引数は複数あっても良い。
type Family<Parent, Child> = {
mother: Parent;
father: Parent;
child: Child;
};
ジェネリック型(型引数を持つ型の宣言)を使用する。
Family<number, string>
は
Family型が持つ2つの型引数Parent, Childにそれぞれ、number, string型を当てはめた型
const obj: Family<number, string> = {
mother: 0,
father: 100,
child: "1000"
};
部分型関係による型引数の制約
type HasName = {
name: string;
};
type Family<Parent extends HasName, Child extends HasName> = {
mother: Parent;
father: Parent;
child: Child;
};
オプショナルな型引数
type Animal = {
name: string;
}
type Family<Parent = Animal, Child = Animal> = {
mother: Parent;
father: Parent;
child: Child;
}
// 通常どおりの使い方
type S = Family<string, string>;
// TはFamily<Animal, Animal>と同じ
type T = Family;
// UはFamily<string, Animal>と同じ
type U = Family<string>;
関数宣言(function declaration)で関数を作る
//function 関数名(引数: 引数の型, ... ): 戻り値の型 { 処理内容 }
function range(min: number, max: number): number[] {
const result = [];
for (let i = min; i <= max; i++) {
result.push(i);
}
return result;
}
console.log(range(5, 10)); // [5, 6, 7, 8, 9, 10] と表示される
関数式(function expression)で関数を作る
type Human = {
height: number;
weight: number;
};
// function(引数: 引数の型, ... ): 戻り値の型 { 処理内容 }
const calcBMI = function(human: Human): number {
return human.weight / human.height ** 2;
};
const uhyo: Human = { height: 1.84, weight: 72 };
// 21.266540642722116 と表示される
console.log(calcBMI(uhyo));
type Human = {
height: number;
weight: number;
};
//Human型を{ height, weight }に分割代入もできる
const calcBMI = function({ height, weight }: Human): number {
return weight / height ** 2;
};
const uhyo: Human = { height: 1.84, weight: 72 };
// 21.266540642722116 と表示される
console.log(calcBMI(uhyo));
アロー関数式(arrow function expression)で関数を作る
type Human = {
height: number;
weight: number;
};
//アロー関数
const calcBMI = ({
height, weight
}: Human): number => {
return weight / height ** 2;
};
const uhyo: Human = { height: 1.84, weight: 72 };
// 21.266540642722116 と表示される
console.log(calcBMI(uhyo));
アロー関数の省略形
式が1文の場合は returnと{}を省略できる
//アロー関数の省略形
const calcBMI = ({
height, weight
}: Human): number => weight / height ** 2;
戻り値にオブジェクトリテラルを使う場合の注意
オブジェクトリテラルを()で囲む。
囲まないと、アロー関数の中身を囲み{}と見なされてしまう。
type Human = {
height: number;
weight: number;
};
type ReturnObj = {
bmi: number
}
// 正しい書き方
const calcBMIObject = ({
height, weight
}: Human): ReturnObj => ({
bmi: weight / height ** 2
});
// これはコンパイルエラーが発生
// エラー: A function whose declared type is neither 'void' nor 'any' must return a value.
const calcBMIObject2 = ({
height, weight
}: Human): ReturnObj => {
bmi: weight / height ** 2;
};
メソッド記法で関数を作る
この記法はオブジェクトリテラルの中で使用することができるプロパティを定義する記法の一種です。
const obj = {
// メソッド記法
double(num: number): number {
return num * 2;
},
// 通常の記法 + アロー関数
double2: (num: number): number => num * 2,
};
console.log(obj.double(100)); // 200 と表示される
console.log(obj.double2(-50)); // -100 と表示される
関数型の記法
関数xRepeatの型は?
(num: number) => string 型
関数型は(引数リスト) => 戻り値の型 という形をとる
const xRepeat = (num: number): string => "x".repeat(num);
引数なし、返り値なしの関数の関数型
() => void
0個以上任意の数の数値を受け取り、数値を返す関数の型
(...args: number[]) => number
type文でF型として別名を定義して、関数の型注釈に使用できる。
type F = (repeatNum: number) => string;
const xRepeat: F = (num: number): string => "x".repeat(num);
関数の返り値の型注釈は省略できる
const xRepeat = (num: number) => "x".repeat(num);
Null 合体演算子 (??)
この演算子は左辺が null または undefined の場合に右の値を返し、それ以外の場合に左の値を返します。
npm install -d と -D の違いに注意
async関数/await式
async関数
返り値は必ずPromiseになる。
例)get3()関数は「3を結果とするPromise」を返す関数である。
async function get3(): Promise<number> {
return 3;
}
const p = get3();
p.then(num => {
console.log(`num is ${num}`);
});
await式
await式はasync関数の中で使える構文。
与えられたPromiseの結果が出るまで待つ
const sleep = (duration: number) => {
return new Promise<void>((resolve) => {
setTimeout(resolve, duration);
});
};
async function get3() {
await sleep(1000);
return 3;
}
const p = get3();
p.then(num => {
console.log(`num is ${num}`);
});
別の例
const sleep = (duration: number) => {
return new Promise<void>((resolve) => {
setTimeout(resolve, duration);
});
};
async function get3() {
console.log("get3が呼び出されました");
await sleep(1000);
console.log("awaitの次に進みました");
return 3;
}
console.log("get3を呼び出します");
const p = get3();
p.then((num) => {
console.log(`num is ${num}`);
});
console.log("get3を呼び出しました");
結果
get3を呼び出します
get3が呼び出されました
get3を呼び出しました
(1秒後)
awaitの次に進みました
num is 3
別の例
const sleep = (duration: number) => {
return new Promise<void>((resolve) => {
setTimeout(resolve, duration);
})
};
async function get3() {
await sleep(1000);
return 3;
}
async function main() {
const num1 = await get3();
const num2 = await get3();
const num3 = await get3();
return num1 + num2 + num3;
}
main().then(result => {
console.log(`result is ${result}`);
});
async関数のいろいろな宣言方法
async function式
const main = async function() {
const fooContent = await readFile("foo.txt", "utf8");
// 2倍にしてbar.txtに書き込む
await writeFile("bar.txt", fooContent + fooContent);
console.log("書き込み完了しました");
};
asyncアロー関数式
const main = async () => {
const fooContent = await readFile("foo.txt", "utf8");
// 2倍にしてbar.txtに書き込む
await writeFile("bar.txt", fooContent + fooContent);
console.log("書き込み完了しました");
};
asyncメソッド記法でのasync関数
const obj = {
// 普通のメソッド
normalMethod() {
// (略)
},
// async関数のメソッド
async asyncMethod() {
// (略)
}
};
ジェネリック関数のバリエーション
const repeat = function<T>(element: T, length: number): T[] {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
const repeat = <T>(element: T, length: number): T[] => {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
const utils = {
repeat<T>(element: T, length: number): T[] {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
}
const pair = <Left, Right>(left: Left, right: Right): [Left, Right] => [left, right];
// pは[string, number]型
const p = pair("uhyo", 26);
const repeat = <T extends {
name: string;
}>(element: T, length: number): T[] => {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
type HasNameAndAge = {
name: string;
age: number;
}
// これはOK
// 出力結果:
// [{
// "name": "uhyo",
// "age": 26
// }, {
// "name": "uhyo",
// "age": 26
// }, {
// "name": "uhyo",
// "age": 26
// }]
console.log(repeat<HasNameAndAge>({
name: "uhyo",
age: 26,
}, 3));
// これはコンパイルエラー
// エラー: Type 'string' does not satisfy the constraint '{ name: string; }'.
console.log(repeat<string>("a", 5));
typeofキーワードで変数の型を得る
型推論の結果を型として抽出・再利用したい場合にtypeofは効果的。
特に、ランタイム(すなわち、型ではない)変数宣言から型推論を通じて型を取り出さるというのは非常に協力な機能。
const obj = {
foo: 123,
bar: "hi"
};
type T = typeof obj;
const obj2: T = {
foo: -50,
bar: ""
};
ネストされたオブジェクトについての注意
スプレッド構文を使用したオブジェクトのコピー
...スプレッド構文は「浅い」ことに注意してください。(シャローコピー)
つまり、1 レベルの深さまでしかコピーされません。
ネストされたオブジェクトの更新
Immer を使用して簡潔な更新ロジックを作成する
Immer
部分型関係(subtyping relation)
部分型関係は、
- ある型をほかの型の代わりに使えるか
- ある型をほかの型とみなせるか
という直感的な概念に基づくものである。
例1)
type FooBar = {
foo: string;
bar: number;
}
type FooBarBaz = {
foo: string;
bar: number;
baz: boolean;
}
const obj: FooBarBaz = {
foo: "hi",
bar: 1,
baz: false
};
const obj2: FooBar = obj;
FooBarBaz型は、FooBar型の部分型である。
つまり、
FooBarBaz型の値は、FooBar型の値でもある。
FooBarBaz型は、FooBar型の上位互換。
- FooBar型が持つプロパティはすべてFooBarBaz型にも存在する。
- 条件1の各プロパティについて、FooBarBaz型におけるそのプロパティの型はFooBar型におけるそのプロパティの型の部分型(または同じ型)である。
例2)
type Animal = {
age: number;
};
type Human = {
age: number;
name: string;
}
Human型はAnimal型の部分型である。
例3)
type AnimalFamily = {
familyName: string;
mother: Animal;
father: Animal;
child: Animal;
}
type HumanFamily = {
familyName: string;
mother: Human;
father: Human;
child: Human;
}
HumanFamily型はAnimalFamily型の部分型である。
配列型の記法
T[] で表現
const arr: number[] = [1, 10, 100];
const arr2: string[] = ["123", "-456"];
Array<T> で表現
const arr3: Array<{
name: string;
}> = [
{ name: "山田さん" },
{ name: "田中さん" },
{ name: "鈴木さん" }
];
異なる型が混ざった配列の型も表現可能
// (string | number | boolean)[]型
const arr4 = [100, "文字列", false];
ジェネリック関数(型引数を持つ関数)
関数宣言
function repeat<T>(element: T, length: number): T[] {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
// ["a", "a", "a", "a", "a"] が表示される
console.log(repeat<string>("a", 5));
// [123, 123, 123] が表示される
console.log(repeat<number>(123, 3));
関数式
const repeat = function<T>(element: T, length: number): T[] {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
アロー関数式
const repeat = <T>(element: T, length: number): T[] => {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
メソッド記法
const utils = {
repeat<T>(element: T, length: number): T[] {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(element);
}
return result;
}
}