# 2022年3月21日 りあクト! 第4章 TypeScriptで型をご安全に(p.149~162)

## 4-1. TypeScript はイケイケの人気言語?
- TypeScriptは、**AltJS**(JSの代替言語。最終的にコンパイラによってJSのコードに変換されます)の1つです。Alternative JavaScriptの略で別のJavaScriptという意味になります。
- GitHut というサイトでは、最新の2020年第3クォーターのプルリクエストベースのランキングでTypeScriptが5位に入っています。(Rubyは7位)
- **Create React App**や**React Native**も公式にTypeScriptをサポートしています。
- **静的型付け**、**型推論**、**Null 安全性**という最近のプログラミング言語のトレンドを押さえつつ、それ以外の部分はそのまま JavaScript と同じ構文です。
- **静的型付け言語**は、コンパイルの段階でnullアクセスエラーになる可能性のコードをチェックして弾いてくれます。(**null安全性**)
- **型推論**とはいちいちプログラマが型を書かなくても、言語処理系が文脈から型を予測して型付けしてくれる機能のことです。
- コーディング中にリアルタイムで問題箇所を指摘してくれるモダンなエディタのVS Code(IDE)が相性が良いです。
- **IDE**とは統合開発環境のことで、コンパイラ、テキストエディタ、デバッガ、などを一つのUIから利用できるようにしたものです。
- SlackもTypeScriptとReactで実装されています。
#### **動的型付け言語と静的型付け言語の違い**
動的型付け言語 : 変数などのデータ型の宣言がいらないプログラミング言語
(値やオブジェクトの型安全性を、実行時に検証する)
- 変数の宣言時に型宣言をする必要がなく、実際にデータが入るタイミング(変数への代入時など)で方が自動的に決まるものです。
静的型付け言語 : 変数などのデータ型の宣言が必要なプログラミング言語
(値やオブジェクトの型安全性を、コンパイル時に検証する)
#### **静的型付け**
- 変数や関数の引数および戻り値がプログラムの実行前にあらかじめ決まっている必要があります。
#### **Null 安全性**
- アクセスエラーになる可能性のあるコードをコンパイルの段階で null チェックして弾いてくれます。(Ruby なら nil に対する NoMethodError)
```typescript
let yano = {
meshi: 'kimuti',
skill: 'JavaScript'
}
// コードを実行する前に、コードを書いた瞬間エラーが出る。
yano.water // => Property 'water' does not exist on type '{ meshi: string; skill: string; }'.(2339)
```
## 4-2. TypeScript の基本的な型
#### 型アノテーション
- 変数に型の注釈を付けることです。
- 型アノテーションを行うと、静的に型付けされた情報はコンパイル時のチェックに用いられ、書かれたコード中に型の不整合があるとコンパイルエラーが起こります。
- `value:type`というフォーマットで予め型を宣言出来るものが**型アノテーション**です。
- 型アノテーションによって型付けしない場合は**型推論**が行われます。
```typescript
// 数値型の変数をnとして宣言し、その変数に3を代入する
let n: number = 3;
// 数値型以外の値を代入するとエラーが出ます
let n: number = '3'; // => Type 'string' is not assignable to type 'number'.(2322)
```
配列の型の中身は全てnumber型の場合の型アノテーション
```typescript
const numarr: number[] = [1, 2, 3];
```
#### 型推論
- コンパイラがその文脈から型を推測することです。
- 型アノテーションを省略しても、コンパイラによって型が推論されます。
JavaScriptの暗黙の型変換
>ある処理において、その処理過程で行われる明示的ではない型変換のこと(JavaScriptPrimer)
```javascript
const s = '123';
> s * 3
> // => 369
```
- 文字列に対して3をかけていますが、計算できているのは文字列が暗黙的に数値に変換されているからです。
#### TypeScrptのプリミティブ型
JavaScriptのプリミティブ型と同じ7種類です。
- Boolean ⇒ true falseの真偽値
- Number ⇒ 数値
- BigInt ⇒ numberでは表現できない大きな数値(253以上)
- String ⇒ 文字列
- Symbol ⇒ シンボル値という固有の識別子を表現する値
- Null ⇒ 何のデータも含まれない状態を明示的に表す
- Undefined ⇒ 未定義であることを表す
#### 配列を型アノテーションを使って宣言する
①配列リテラルを使う方法
```typescript
// String型の配列を作成して変数yanoに代入する。
const yano : string[] = ['kimuchi', 'tofu', 'nattou']
```
②ジェネリクス`<>`を使う方法
```typescript
const yano : Array<string> = ['kimuchi', 'tofu', 'nattou']
```
①のリテラルを使う方法が推奨されているのでジェネリクスの書き方はこういった書き方もできるという認識だけ持っておけば良いでしょう。
#### 型アノテーションにオブジェクトを使うときの書き方
Objectはプリミティブ型以外の全てのオブジェクトのプロトタイプなのでobjectと型アノテーションに定義すると広義になり意味をなさないものになります。
```typescript
const words: object = ['a', 'b', 'c'];
```
狭義のオブジェクトの型を定義する書き方
```typescript
const red: { rgb: string, opacity: number } = { rgb: 'ffff', opacity: 1}
```
- オブジェクトリテラルを使い、それぞれのキーに対して型アノテーションをつけています。
このように毎回インラインで書くのが大変なため
**インターフェース**というオブジェクトの型に名前を付ける宣言の仕方があります。
```typescript
// colorというインターフェースを作成し、オブジェクトで使われるであろうキーとその型を宣言する
interface Color {
readonly rgb: string;
opacity: number;
name?: string;
}
// turquoiseというオブジェクトにcolorインターフェースを適用したプロパティとその値を代入する。
const turquoise: Color = { rgb: '00afcc', opacity: 1 };
// turquoiseオブジェクトのnameプロパティに文字列型の値を代入
turquoise.name = 'Turquoise Blue';
turquoise.rgb = '03c1ff'; // error TS2540: Cannot assign to 'rgb' because it is a read-only property.
```
- `?` をつけるとそのキーは省略できます。今回の例では`name`です。
- `readonly`をつけると書き換え不可になります。
#### インデックスシグネチャ
- `[key: T]: U`という構文は、**インデックスシグネチャ**と呼ばれます。これは、オブジェクトがより多くのキーを含む可能性があることをTypeScriptに伝える方法です。
- キーを文字列と数値の2種類なら何が入っても使用できるというものです。
```typescript
interface Status {
[attr: number]: boolean;
}
const myStatus: Status = {
1: true,
2: false,
3: true
}
console.log(myStatus);
```
- キーは`number`型なら何でも許可し、バリューは`boolean`型を指定しています。
#### TypescriptのEnum型、共用体型
enumは宣言とキー名を最初の文字を大文字で書くという慣例があります。
**リテラル型とは**
> TypeScriptではプリミティブ型の特定の値だけを代入可能にする型を表現できます。そのような型をリテラル型と呼びます。
- 任意の文字列以外を許さない型です。
- オブジェクトリテラルとか配列リテラルとかのリテラルではありません。
- リテラル型として表現できるものは、以下のようになります。
1. 論理型のtrueとfalse
2. 数値型の値
3. 文字列型の文字列
```typescript
// boolean型の中でもtrueの値しか受けつけないようにする
const isTrue: true = true;
// number型の中でも123の値しか受け付けないようにする
const num: 123 = 123;
// String型の中でも文字列fooの値しか受け付けないようにする
const str: "foo" = "foo";
```
- JavaScriptの**const**を使って、変数宣言をするとTypeScriptでは**リテラル型**として型推論されます。なぜなら、constで宣言された変数には**再代入できない**ため自動的にリテラル型として初期化された値しか受け付けないようになるのです。
```typescript
let x = 1; // let x: number
const y = 1; // const y: 1
```
**共用体型**
```typescript
let x: number | string;
x = 'yano';
x = 2;
x = true // => Type 'boolean' is not assignable to type 'string | number'.
```
- 変数xは`number` または`string` のみを受け付けるという型を指定できています。
**リテラル型を 演算子`|`で並べる事で列挙型のように扱う**
```typescript
let asakai: 'yui' | 'yuki' | 'yano' = 'yui';
console.log(asakai); // => yui
asakai = other; // => [ERR]: other is not defined
```
- 変数`asakai`で列挙したリテラル型以外は受け付けないという書き方ができます。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.149~162](https://oukayuka.booth.pm/items/2368045)
[サバイバルTypeScript-リテラル型](https://typescriptbook.jp/reference/values-types-variables/literal-types)
# 2022年3月22日 りあクト! 第4章 TypeScriptで型をご安全に(p.162~168)
## タプル型
#### タプル型とは
- **タプル型**とは、配列内の要素の型と、その順番や個数があらかじめ指定できる**特殊な配列の型**です。要素の型は、それぞれの要素ごとに決めることが出来ます。
- **レストパラメーター**を使うと、いくつでも引数を受け取ることができるようになります。
※ Rest parametersは、仮引数名の前に...をつけた仮引数のことで、残余引数とも呼ばれます。 Rest parametersには、関数に渡された値が配列として代入されます。
- **タプル型**を使うことによって、引数などとして受け取る値の制限ができるイメージです。より厳密な受け取りたい値を指定できるので、バグにつながる値の受け取りを排除できます。
以下の場合にタプル型が使用されます。
- 関数の引数
- 関数の引数としてまとめて配列で渡したい時に仮引数としてタプル型であらかじめ型、個数、順番の要素を定義して渡すイメージです。
- API関数の戻り値(分割代入を使うことが多いです。)
```typescript
const charAttrs: [number, string, boolean] = [1, 'party', true];
```
```typescript
let array0: [number, string] = [1, 'a'];
let array1: [number, ...string[]] = [1, 'a', 'b'];
// タプル型の内部で、...string[]が使われている場合、
// string型の要素を追加できます
array1[3] = 'c';
let array2: string[] = ['a', 'b'];
array2[3] = 'c';
```
- array0のarray[0]には、number型、array[1]にはstring型の要素のみを受け付るようにしています。
- array1では**レストパラメーター**を使っているので、第一要素以外は、string型の要素を幾つでも受け付けることが出来ます。
- array2では、String型の要素を受け付けることができる配列となっています。
#### 使用用途
まとめて配列で実引数を渡すときに、仮引数にまとめて型アノテーションをつけられるので関数の利用時によく使われます。
また、仮引数にRestParametersを使用する事もできます。
仮引数の第一要素、第二要素の順番で型アノテーションをつけられて、RestParametersを使わなければ引数の数も指定できます。
## Any, Unknown, Never
何者でもあったり、何者でもなかったりする型です。
#### Any型とは?
- **Any型**とは、いかなる型の値を受け付けるようにできる型のことです。JavaScriptと同じ挙動になってしまいます。
- データ型が不明なまま書く必要がある場合に使われますが、問題点もある型なので次で説明する**unknown**を使う方が良いでしょう。
- any型の変数については、コンパイラーが型チェックを行いません。実行してみるとエラーになるようなコードでも、コンパイラーはその問題を指摘しないのです。
JSON.parse()の戻り値は、Object, Array, 文字列, 数値, 論理値, null値です。そのため、JSON.parse()の戻り値の型は、TypeScriptによってany型と推論されます。
user.address.zipCode の ようなアクセスもコンパイラは通してしまいます。
```typescript
const str = '{"id": 1, "username": "john"}';
const user = JSON.parse(str);
console.log(user.id, user.adress.zipCode); // => [ERR]: Cannot read properties of undefined (reading 'zipCode') undefined
```
- 通常TypeScriptでは、値の代入前に型指定をするため、誤った型の値を入れようとした時点で、エラーを出します。
- anyを使うと、コンパイル時にエラーを出さずに実行時にエラーが発生してしまいます。
- 今回の場合は、JSON.parseのメソッドの戻り値がany型で返されるため、代入した変数のuserもanyとして型推論されています。
- any型で返されるのは、JSON.parse()の返り値が`Object, Array, 文字列, 数値, 論理値, null 値のいずれか`の様々な値を返せるため、**TypeScript**の型推論でany型に変換されるということです。
- TypeScriptの静的型付け言語の恩恵が受けられていません。
any型は、unknownとは違いプロパティやメソッドを使用できるが、any型の使用 = TypeScriptが型のチェックを放棄した型となるので、Typescriptの利点を活かしきれていません。
#### unknown型
- anyの型安全版です。任意の型の値を代入できる点はany型と同じです。
- 何のプロパティもプロトタイプメソッドも持たない型です。
- 今回userまでは代入できますが、userにプロパティ名を指定して呼び出そうとするとコンパイルの時点でエラーになります。
```typescript
const str = '{"id": 1, "username": "john"}';
const user: unknown = JSON.parse(str);
console.log(user); // => [LOG]: {
"id": 1,
"username": "john"
}
```
#### never型
- 何者も代入できない型です。
使用用途が想像しづらい型ですが、書籍にあった例では
```typescript
const greet = (friend: 'Serval' | 'Caracal' | 'Cheetah') => {
switch (friend) {
case 'Caracal':
return `Hi, ${friend}!`;
case 'Cheetah':
return `Hiya, ${friend}!`;
default: {
const check: never = friend;
}
}
};
console.log(greet('Serval'));
```
- greetの仮引数にはリテラル型でServalは許容されていますがcase文ではありません。
- 実引数にServalを渡しているのでswitch文ではdefaultが呼び出されます。
- デフォルトにはnever型を指定しているので何者も代入できないのでエラーになります。case文の漏れをチェックできます。
## 関数とクラスの型
- TypeScriptではコンパイラオプションに`"noImplicitAny": true`が指定されていないと引数の型定義がない場合に暗黙的に**any型**があてがわれてコンパイルが通ってしまいます。この設定をtrueにしないと、any型と推論する値(関数の引数など)に対してエラーを出しません。
- 関数は、様々な値を受け取ることが出来るため、TypeScriptではany型と型推論されてしまいます。
tsconfig.jsonファイル
```typescript
"noImplicitAny": true,
```
### 関数の型定義
関数では、引数と戻り値に対して**型アノテーション**を設定します。戻り値の型は型推論できますが、戻り値の型を明示するのが昨今のトレンドなので、本ブログでも明示します。
関数における、引数と戻り値の型を別々に定義する記法でまず紹介します。
```typescript
const add = (n: number, m: number): number => n + m;
```
- arrow関数を使って、関数を定義し変数addに格納しています。
- `(n: number, m: number)`で引数の型を指定しています。
- `(n: number, m: number)`の後ろの: numberで戻り値の型を指定しています。
```typescript
const hello = (): void => {
console.log('Hello');
};
hello(); // => Hello
```
- arrow関数を使って関数を定義しています。
- 戻り値は何も返さないので戻り値の型アノテーションは何も返さない`void`になります。
### 呼び出し可能オブジェクト(呼び出しシグネチャ)
**呼び出し可能オブジェクト**として定義すると、関数の引数と戻り値の型をまとめて定義できます。
**呼び出し可能オブジェクト**とは、どのような関数なのかを表現する型定義のことです。
これをインターフェースとして定義すると、関数定義の際にスッキリとした記述ができることを以下で説明しています。
**インターフェース**はオブジェクトの型付けを行うことができる機能です。
**インターフェースを呼び出し可能オブジェクトとして定義し、それを関数式に使う定義法**⏬
```typescript
// 呼び出し可能オブジェクトをインターフェースとして定義する
interface Mononoke {
(name: string): string;
};
// 呼び出し可能オブジェクトを明示的に書く場合
const getMononoke1 = (name: string): string => {
return name;
}
// 呼び出し可能オブジェクトをインターフェースとして定義したものを使う場合
const getMononoke2: Mononoke = (name) => {
return name;
}
```
- インターフェースMononokeで、関数の引数の型と戻り値の型を指定しています。今回の場合、引数nameは、string型、戻り値は、string型を指定しています。
- getMononoke2では、インターフェースMononokeを使っているので、関数の仮引数`(name)`の型を指定しないでスッキリと記述できます。
## 呼び出し可能オブジェクトの省略記法
```typescript
// 呼び出し可能オブジェクトの省略記法
(message: string, userId?: string) => void;
// 明示的な呼び出し可能オブジェクト
{
(message: string, userId?: string): void
}
```
- オブジェクトリテラルをつけないならば、`=>`をつけます。
- オブジェクトリテラルをつけるなら従来通りの関数の型アノテーションと同様の記法になります。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.162~168](https://oukayuka.booth.pm/items/2368045)
[【TypeScript入門】呼び出しシグネチャとは](https://qiita.com/YSasago/items/e36f9d6f77c1b4ba7b53)
# 2022年3月24日 りあクト! 第4章 TypeScriptで型をご安全に(p.169~171)
#### 関数の型宣言にジェネリクスを用いる記法
```typescript
const toArray = <T>(arg: T, arg2: T): T[] => [arg1, arg2];
toArray(8, 3);
```
- 第一引数の実引数が型推論されてTがnumber型になります。
- このTは型引数(Type Prameter)でありアルファベットは何を使用しても構いません。
このようにデータ型に束縛されないよう型を抽象化してコードの再利用性を向上させつつ、静的型付け言語の持つ型安全性を維持するプログラミング手法を**ジェネリックプログラミング**と呼びます。
そして、型引数を用いて表現するデータ構造のことを**ジェネリクス**といいます。
```typescript
const toArrayVariably = <T>(...args: T[]): T[] => [...args];
toArrayVariably(1, 2, 3, 4, 5);
//=> [1,2,3,4,5]
//=> <number>(...args: number[]): number[] => [...args];
toArrayVariably(6, '7', 8);
// =>[eval].ts:3:20 - error TS2345: Argument of type '"7"' is not assignable to parameter of type'number'.
```
- 変数toArrayVariablyは、同じ型の引数を制限なく受け取り、同じ型の戻り値を配列として返す関数が代入されています。制限なく引数を受け取れるのは、レストパラメーターを使っているからです。
- 変数toArrayVariablyに代入されている関数の引数と戻り値に、ジェネリックスで型付けされています。
- `<T>`の部分で、型引数Tを宣言しています。宣言後で、型引数 `T`が使えるようになっています。Tは、抽象的な型を表しています。
- `toArrayVariably(1, 2, 3, 4, 5);`の部分では、第1引数として渡された値が`1`であるため、number型として型推論されて型引数の型が決まります。
- TypeScriptでは引数の型をあらかじめ指定しないといけませんが、上記のように型引数を使うことで型を固定しないで柔軟に定義出来ます。
## TypeScriptでのクラスの扱い
- TypeScriptのクラスはコンストラクタ関数内の変数の型アノテーションを最初に定義する必要があります。
- プロパティ初期化子(= 値)を使えばコンストラクタに引数がないクラスならばコンストラクタを省略してインタンス化できます。
- プロパティの初期化子を使用する際に型推論を使うこともできます。
- プロパティの初期化子を使う際にreadonlyなどの修飾子を使うこともできます。
- プロパティの初期化子を使う際にアクセス修飾子(public protected private)
- あらかじめ、クラスで使うプロパティとその型を決めてしまうようなものです。
クラス内でプロパティ初期化子を使用した例を、以下に示します。
```typescript
class Person {
// TypeScriptでは。メンバー変数をクラスの最初で宣言する
// ここで宣言しないと、エラーが起こる
// = 値は、プロパティ初期化子と呼ばれる
// プロパティ初期化子を書いた場合、コンストラクタの記述を省略できる
// 初期化子は、クラスがインスタンス化されるときに自動的に実行されます。
readonly name = 'yano';
skill = 'JavaScript';
}
const a = new Person();
console.log(a);
// => [LOG]: Person: {
// "name": "yano",
// "skill": "JavaScript"
// }
```
- Personクラスは、値の書き換え不可である初期値がyanoというnameプロパティとJavaScriptという初期値が入ったskillプロパティをあらかじめ持っています。
- nameプロパティは、リテラル型として型推論されます。(readonly修飾子を使っているため)ここでは、型推論を使っていますが、明示的に、`:type`で型を提示することもできます。
- skillプロパティは、String型の`'JavaScript'`が初期値として代入されているのでString型として型推論されます。こちらも`:type`で型を提示できます。
- Personクラスのインスタンスを生成すると、引数としてコンストラクタにプロパティの値を渡さなくともnameプロパティとskillプロパティを最初から持ったインスタンスが生成できます。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.169~171](https://oukayuka.booth.pm/items/2368045)
[フィールドの初期化子 (initializer)
](https://typescriptbook.jp/reference/object-oriented/class/field-initializers)
# 2022年3月25日 りあクト! 第4章 TypeScriptで型をご安全に(p.171~)
## アクセス修飾子
アクセス修飾子をプロパティの宣言時につけるとそのプロパティがどこからどこまでアクセスできるのかを指定できます。
アクセス修飾子を何も指定しない場合は、暗黙的にpublicとなります。
|アクセス修飾子|内容|
|---|---|
|public|自クラス、子クラス、インスタンス全てからアクセス可能。デフォルトでは全てのメンバーがこの`public`になる|
|private|自クラスからのみアクセス可能|
|protected|自クラス、子クラスからアクセス可能|
## アクセス修飾子 private
自クラスからのみアクセス可能なアクセス修飾子です。
```typescript
class Yano {
constructor(private _name: string) {
}
say(): void {
console.log(this._name + "です")
}
}
const yano = new Yano("やの");
console.log(yano._name);
```
- 宣言時にprivate修飾子を書きます。
- インスタンス化して呼び出そうとした場合、以下のコンパイルエラーが発生しました。
Property '_name' is private and only accessible within class 'Yano'.(2341)
JavaScriptPrimerにありました。
> 外から直接読み書きしてほしくないプロパティを_(アンダーバー)で開始するのはただの習慣であるため、構文としての意味はありません。
## アクセス修飾子 protected
自クラス、子クラスからアクセス可能なアクセス修飾子です。
```typescript
class Person {
// protectedでnameを宣言する
protected name;
constructor(name: string) {
this.name = name;
}
}
class Children extends Person {
constructor(name: string) {
super(name);
}
method() {
// protectedで宣言されているので、 子クラスでもnameにアクセス可能です
console.log(this.name);
}
}
const a = new Person('yano');
// インスタンスからはアクセス不可能です
console.log(a.name); // => Property 'name' is protected and only accessible within class 'Person' and its subclasses.(2445)
```
## クラスは継承よりも合成
まずは継承や合成の元となるRectangleクラスを定義します。
```typescript
class Rectangle {
readonly name = 'rectangle';
sideA: number;
sideB: number;
constructor(sideA: number, sideB: number) {
this.sideA = sideA;
this.sideB = sideB;
}
getArea = (): number => this.sideA * this.sideB;
}
const yano = new Rectangle(3, 4);
console.log(yano.getArea());
```
出力結果: 12
以下のパターンが継承です。
Rectangleクラスを継承したSquareクラスを定義しています。
```typescript
class Square extends Rectangle {
readonly name = 'square';
side: number;
constructor(side: number) {
super(side, side);
}
}
```
- 継承の場合、sideAやsideBなどの不必要なメンバー変数まで継承してしまい、バグの元になりかねないというデメリットがあります
- getArea() メソッドが 完全に共有されているため、親クラスの実装を不用意に変更出来ないため保守性が悪いです。
- 子クラスでnameプロパティを新たに定義する場合、親クラス Rectangle の name プロパティから readonly 修飾子を削除する必要があります。
以下のパターンが合成です。
Rectangleクラスを独立したただの部品として扱っています。
```typescript
class Square {
readonly name = 'square';
side: number;
constructor(side: number) {
this.side = side;
}
getArea = (): number => new Rectangle(this.side, this.side).getArea();
}
```
- Rectangleクラス内部の実装を知る必要はなく使いたい部分の仕様さえ知っていれば良いです。
- 依存がないため、合成元の内部の変更に影響されません。
## クラスの継承と合成(別の例)
```typescript
class Yunosuke {
live = 'thiba';
likeCountry = 'thai';
a: string;
b: string;
constructor(a: string, b: string) {
this.a = a;
this.b = b;
}
getArea = (): string => this.a + this.b;
}
const yuno = new Yunosuke("1","2");
console.log(yuno);
console.log(yuno.getArea());
class Yuki extends Yunosuke {
constructor(a: string){
super(a,a);
}
}
const yuki = new Yuki("3");
console.log(yuki);
class Yano {
a: string;
constructor(a: string) {
this.a = a;
}
getArea = (): string => new Yunosuke(this.a, this.a).getArea();
}
const yano = new Yano("5");
console.log(yano.getArea());
```
継承の問題点
- YukiさんはYunosukeさんを継承しているのでYunosukeさんが引っ越しをして`live` が福岡になってしまったらYukiさんの福岡に行ってしまいます。
合成にするべき理由
- YanoさんはYunosukeさんのメソッドを合成していてextendsやsuperがないので独立しています。
- Yunosukeさんが引っ越したとしてもYanoさんが引っ越すことはありません。
- Yunosukeさんの変更に影響されずに使いたいメソッドだけを使う事ができています。
Reactでは現在、関数コンポーネントを使っていてクラスコンポーネントは非推奨なのですが、書籍には以下のようにありました。
Reactでもコンポーネントをクラスで作成するときは、継承を避けるよう公式ドキュメントに書かれています。
> Facebook では、何千というコンポーネントで React を使用していますが、コンポーネント の継承による階層構造を作ることが推奨されるケースはひとつもありませんでした。
props と composition(合成)は、コンポーネントの見た目とふるまいを明示的かつ安全に カスタマイズするのに必要十分な柔軟性を備えています。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.171~](https://oukayuka.booth.pm/items/2368045)
# 2022年3月26日 りあクト! 第4章 TypeScriptで型をご安全に(p.174~)
## クラスの2つの顔
TypeScriptには、クラスの型を抽象化して定義する方法が2つあります。
- abstract修飾子を用いて抽象クラスを定義する方法
- インターフェースを使用する方法
## abstract修飾子
abstractは抽象クラスを定義する修飾子です。
**抽象クラス**とは、それ自身がインスタンスを生成出来ず、継承することを前提としたクラスです。
抽象クラスはその定義に実装を含むことができてしまうため、実装を伴った継承を避けるためにもabstract修飾子を用いてクラスの型を抽象化して定義することは推奨されていません。
クラスの実装を伴わずに型だけを適用したい場合にはインターフェースが推奨されています。
## インターフェースを用いてクラスの型を抽象化して定義する
インターフェースを用いてクラスの型を定義する例です。
Rectangleクラスの宣言時にimplementsを書くことでインターフェースShape、Quadrangleを適用しています。
```typescript
interface Shape {
readonly name: string;
getArea: () => number;
}
interface Quadrangle {
sideA: number;
sideB?: number;
sideC?: number;
sideD?: number;
}
class Rectangle implements Shape, Quadrangle {
readonly name = 'rectangle';
sideA: number;
sideB: number;
constructor(sideA: number, sideB: number) {
this.sideA = sideA;
this.sideB = sideB;
}
getArea = (): number => this.sideA * this.sideB;
}
const rec = new Rectangle(5, 5);
console.log(rec.name);
console.log(rec.sideA);
console.log(rec.getArea());
```
▼出力結果
"rectangle"
5
25
- Quadrangle インターフェースの getArea の定義に呼び出しオブジェクトの省略記法のアロー構文を使いましたが、getArea(): number という書き方も出来ます。
- アロー構文の場合は、オーバーロードが出来ないため、オーバーロードする予定がないメソッドの型定義はアロー構文で書くことで意図が分かりやすくなると本書では推奨されています。
#### implements
クラスを宣言するときに、implementsを使うと、そのクラスが特定のインターフェースを満たしていることを表現できます。
以下のコードブロックでは、Catクラス内でメンバー変数colorが定義されていません。そのため、CatクラスがインターフェースAnimalの内容を満たさず、エラーが出ています。
```typescript
interface Animal {
name: string;
age: number;
color: string;
};
class Cat implements Animal {
// => Class 'Cat' incorrectly implements interface 'Animal'.
// Property 'color' is missing in type 'Cat' but required in type 'Animal'.(2420)
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const cat = new Cat('kevin', 1);
console.log(cat);
```
## クラス定義したものをインターフェースのように扱う
```typescript
class Point {
// プロパティ初期化子
// numberを省略しても、0を代入しているので、
// xとyはnumberとして型推論される
x: number = 0;
y: number = 0;
}
const pointA = new Point();
// PointはPointインスタンスと同じ構造を持つオブジェクトの型
const pointB: Point = { x: 2, y: 4 };
interface Point3d extends Point {
z: number;
}
const pointC: Point3d = { x: 5, y: 5, z: 10}
```
- poitAはclass Pointをインスタンス化しています。
- pointBはclass Pointを型定義で使いインターフェースと同じ振る舞いをしています。
- Point3dというインターフェースにclass Pointを継承させています。
**TypeScriptではクラス構文を使うとクラスとインターフェースの2つの定義ができており、文脈によって振る舞いが変わります。**
コード例のようにコンストラクタ関数を宣言することと、クラスをインターフェースとして扱うことが出来ます。
よって、この書籍では『クラスには2つの顔がある!』と言われています。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.174~](https://oukayuka.booth.pm/items/2368045)
# 2022年3月28日 りあクト! 第4章 TypeScriptで型をご安全に(p.177~)
## 型エイリアス VS インターフェース
型エイリアスの場合
```typescript
type Yuno = 'Yui';
type Yuno = 'Yano'; // エラーになります。
```
インターフェースの場合
```typescript
interface Yuno {
Yui: string;
}
interface Yuno {
Yano: string;
}
```
型エイリアスとインターフェースの違いを以下にまとめます。
#### 型エイリアス
- 型エイリアスの構文は参照のための別名をつける式です。
- 同じ名前の型エイリアスを宣言することはできません。
- 任意の型を指定できます。
- 代入演算子を使った式になるので関数式と同じ理屈でセミコロンは必要です。
- マップ型や条件付き型といった高度な型構文が記述可能です。
#### インターフェース
- interfaceの構文は、オブジェクトとクラスの型に名前をつける宣言です。
- 同じ名前のインターフェースを宣言できます。それらは自動的にマージされて、1つのインターフェースになります。
- オブジェクトとクラスの型のみ定義できます。
- インターフェース構文はブロックで終わる文なので、セミコロンは不要です。
型エイリアスは任意の型に参照のための別名を与えるので、再利用出来ます。
```typescript
type Asakai = 'A' | 'B' | 'C';
type Yui = {
yuno: Asakai;
yuki: string;
}
```
- リテラル型を列挙した共用体型をAsakaiとして別名をつけています。
- Yuiという型エイリアスの中の`yuno` にAsakaiの型をつけています。
## 型エイリアスを使いましょう
- インターフェースは拡張性があり同じ名前の型に後からプロパティを追加できる性質は開発の面でバグに繋がりやすいです。
- これから学ぶ高度な型構文が記述できますがインターフェースではできません。
- 一貫して型エイリアスを使う事で保守性の高いコードが書けます。
## 共用体型と交差型
以下に、共用体型と交差型の特徴をまとめます。
### 共用体型
```typescript
type Yuno = {
foo: number;
bar?: string;
};
type Yano = {
foo: string;
};
type Yuki = {
baz: boolean
};
type fusionA = Yuno | Yano; // { foo: number | string; bar?: string }
type fusionB = Yuno | Yuki; // { foo: number; bar?: string } or { baz: boolean }
```
- 型エイリアスを共用体型で定義しても、どちらかが適用されます。
- 合成はされません。
### 交差型(インターセクション型)
- プリミティブ型のみを用いて交差型を作ると、never型として推論されます。以下の例では、number型とstring型を同時に満たす型が存在しないので、never型と推論されています。
```typescript
// type Some = neverと表示される
type Some = number & string;
```
- オブジェクトの型を用いて交差型を作ると、内部のプロパティの型がマージされたオブジェクトの型になります。
```typescript
type A = {foo: number};
type B = {bar: string};
type C = {
foo?: number;
baz: boolean;
};
type AnB = A & B; // {foo: number, bar: string}
// 必須プロパティと省略可能プロパティが交差したら、必須の方が優先される
type AnC = A & C; // {foo: number, baz: boolean}
type CnAorB = C & (A | B); // {foo: number, baz: boolean} or {foo?: number, bar: string, baz: boolean}
```
- type AnC型のfooプロパティのように、同じ型でありながら省略可能(?)が交差した場合は必須のプロパティの型が優先されます。
- 内部のプロパティの型がマージされるイメージを持つと良いです。
- 同じ型で`?` を使ったものと使わないものが交差したら必須である型が優先されます。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.177~](https://oukayuka.booth.pm/items/2368045)
# 2022年3月29日 りあクト! 第4章 TypeScriptで型をご安全に(p.183~)
## 型のNull安全性を保証する
TypeSctriptはデフォルトの設定では全ての型にnullとundefinedを代入出来てしまいます。
これではnull安全性が保証されず、静的型付け言語を使っているのに実行エラーが頻発しかねません。
厳密にnullやundefindと他の方を厳密に区別するにはstrictNullCheckをコンパイルオプションで設定します。
なぜデフォルトで有効になっていないかというと、過去に書かれたコードがバージョンを上げることで有効になるためコンパイルエラーになるためです。。
#### tsconfig.jsonでnullとundefinedを他の型と区別する設定にする
tsconfig.jsonファイル
```typescript
{
//省略
"strictNullChecks": true
}
```
- この設定を加えることで`null`と`undefined`を他の型と区別することができます。
- 設定していないとどの型にも`null`と`undefined`は代入できてしまいます。
**※`strict true`の設定にも含まれています。**
#### nullを許容する場合の書き方
```typescript
let foo: string | null = 'fuu';
foo = null;
```
- 変数fooに対して`string`と`null` の共用体型を指定しています。
null安全性が保証されることで
以下のコードでは、getMomNameの定義でコンパイルエラーが発生します。
```typescript
type Resident = {
familyName: string;
lastName: string;
mom?: Resident;
};
// Object is possibly 'undefined'.(2532)
const getMomName = (resident: Resident): string => resident.mom.lastName;
const patty = { familyName: 'Hope-Rabbit', lastName: 'patty'};
```
- Resident型のmomが省略可のため、getMomNameに渡されるオブジェクトのmomが省略されている可能性があるためコンパイルエラーが発生します。
- 「この場合だとnullやundefinedが入るかもしれませんよ?」というエラーです。
(下で説明される非Nullアサーション演算子はそれに対して「絶対に入らんからええねん!」という記法です。)
推奨はされていませんが
`null`も`undefined`も入らないことをコンパイラに伝えるためには、非Nullアサーション演算子を使います。ただし、コンパイラを黙らせているだけで、実行時にエラーになる可能性があり静的型付け言語の恩恵が受けられなくなってしまうため、書籍でのチームでは使わない方針という説明がされていました。
```typescript
// !が非Nullアサーション
// resident.momでundefinedとnullを返さないことを強制する
const getMomName = (resident: Resident): string => resident!.mom.lastName;
```
- なぜデフォルトで設定されていないかというと、null安全性が保証されない事が当然の時代に書かれたコードが、この設定が追加された事でコンパイルエラーになってしまうため、後方互換性を持たない言語になってしまうからです。 by aniki
**なぜデフォルトで設定されていないのか**
過去はnull安全性が保証されていない言語だったため、バージョンを上げた際も過去に書かれたコードとの後方互換性を保てるように任意で設定する仕様となっています。
by YANOYANO
デフォルトでは、strictNullCheckはfalseに設定されています。理由は、過去のコードとの後方互換性を保つためです。(YUKI)
デフォルトでstrickNullChecksが無効になっているのは、過去のコードとの後方互換性のためです。(yui)
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.183~](https://oukayuka.booth.pm/items/2368045)
# 2022年3月31日 りあクト! 第4章 TypeScriptで型をご安全に 4-5 さらに高度な型表現(p.186~)
## 型表現に使われる演算子
#### typeof演算子
```typescript
console.log(typeof 100); //'number'
const arr = [1, 2, 3];
console.log(typeof arr); //'object'
type NumArr = typeof arr;
const val: NumArr = [4, 5, 6];
const val2:NumArr=['foo','bar','baz'];
```
- 変数arrに[1, 2, 3]を代入(ここで型推論が行われる)して、typeofで型を抽出(配列のnumber型)したものをtype NumArrに型エイリアスで定義しています。(変数に代入した場合と同じように扱えます)
- 変数valにNumArrの型定義をする事で、配列のnumber型を定義した事になります。
- 通常の式では渡された値の型の名前を文字列として返します。(一行目)
-
#### in演算子
in演算子を型コンテキストで使うと、共用体型の各要素の型をキー、任意の値をバリューとするマップ型(Mapped Types)というものを作ります。インデックスシグネチャと違って、プロパティ名を制限できます。
```typescript
const obj = { a: 1, b: 2, c: 3};
console.log('a' in obj); // true
for (const key in obj) { console.log(key); } // abc
type Fig = 'one' | 'two' | 'three' ;
// ?がない場合、全てのプロパティを省略できません。
type FigMap = { [k in Fig]?: number };
const figMap: FigMap = {
one: 1,
two: 2,
three: 3,
};
figMap.four = 4: // coompile error!
```
- 通常の式では指定した値がオブジェクトのキーとして存在するか どうかの真偽値を返します。
- 通常の式でのfor...in文はオブジェクトのkeyを反復処理で取り出します。
- 型コンテキストでは列挙された共用体型の各要素の型の値を抜き出してマップ型(Mapped Types)というものを作ります。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.186~](https://oukayuka.booth.pm/items/2368045)
# 2022年4月1日 りあクト! 第4章 TypeScriptで型をご安全に 4-5 さらに高度な型表現(p.186~)
## keyof演算子
通常の式では使えず、型コンテキストのみで用いられる演算子です。オブジェクトの各キーを列挙した共用体型になります。
```typescript
const permissions = {
r: 0b0100,
w: 0b010,
x: 0b001,
};
// typeof permissionsで、オブジェクトの型が取得できます。
// さらにkeyofを使うことで、オブジェクトの型のキーを抜き出して、共用体型を作ります。
type PermsChar = keyof typeof permissions;
// 0b0100,0b010, 0b001は、2進数です。
// インデックスアクセス演算子は型に対して使用します。
// 下のconstで宣言したpermissions1は変数です
// typeofでオブジェクトの型を抽出して、その型に対して、インデックスアクセス演算子を使っています。
const permissions1 = {
r: 0b0100 as const,
w: 0b010 as const,
x: 0b001 as const,
};
type PermsChar1 = keyof typeof permissions1;
// => "r" | "w" | "x"
type PermsProp = typeof permissions1['x'];
// => type PermsProp = 1
type PermsNum = typeof permissions1[PermsChar1];
// => type PermsNum = 4 | 2 | 1
```
## 配列の要素から型を作る
```typescript
const species = ['a', 'b', 'c'] as const;
// インデックス番号で欲しい値を取得できます。
type Species = typeof species[0]; // "a"
type Species = typeof species[1]; // "b"
// 全てを取得するにはnumberを指定します。
type Species = typeof species[number];
// "a" | "b" | "c"
```
- as constをつけない場合はstring[]の型として型推論されます。
- 配列のインデックス番号を指定することで該当する要素のリテラル型を抽出出来ます。
- インデックスの代わりにnumberを使うことで全ての要素の型を取り出すことが出来ます。
## オブジェクトのValueの型を抽出する
```typescript
const permissions = {
r: 0b100 as const,
w: 0b010 as const,
x: 0b001 as const
}
type Perms = typeof permissions;
// => type Perms = {
// r: 4;
// w: 2;
// x: 1;
// }
// 型を関数のように扱う
type ValueOf<T> = T[keyof T];
type PermsNum1 = ValueOf<Perms>;
// => 4 | 2 | 1
type PermsChar = keyof Perms
// => "r" | "w" | "x"
type PermsNum2 = typeof permissions[PermsChar];
// => 4 | 2 | 1
```
- `typeof permissions`でのオブジェクト全体の型を抽出します。
- `T[key of T]`でobj[keyof T]でobj[インデックス番号]といった記法になり、オブジェクトのバリューを取得できます。
# 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.188~](https://oukayuka.booth.pm/items/2368045)
# 2022年4月2日 りあクト! 第4章 TypeScriptで型をご安全に 4-5 条件付き型とテンプレートリテラル型(p.190~)
## extends
クラスやインターフェースの拡張に使われるextendsキーワードは型引数の表現にも使うことが出来ます。
```typescript=
const override = <T, U extends T>(obj1: T, obj2: U): T & U => ({
...obj1,
...obj2,
});
override({a: 1}, {a: 2, b: 8}); // { a: 2, b: 8 }
override({a: 2}, {x: 7}); // compile error!!
```
- (obj1: T, obj2: U)の仮引数に実引数に渡したオブジェクトが入ります。
- 型引数のTは第一引数のオブジェクトの型です。
- (ここがポイント)型引数の第二引数はUの型が第一引数Tの型を同じか拡張したものであることを表現しています。
- 関数内の処理でスプレッド構文を使用しており、同名のプロパティ名の値は上書きされています。(Reactでよく使う記法になるので覚えておくと良いでしょう)
## 条件付き型 (Conditional Types)
三項演算子を併用することで任意の条件による型の割り振ることが出来ます.
これを条件付き型と呼びます。
```typescript
<T extends U? X:Y>
```
オブジェクトの型から任意のプロパティの型を抽出する例です。
```typescript
type User = { id: unknown };
type NewUser = User & { id: string };
type OldUser = User & { id: number };
type Book = { isbn: string };
type IdOf<T> = T extends User ? T['id'] : never;
type NewUserId = IdOf<NewUser>; //string
type OldUserId=IdOf<OldUser>; //number
type BookId = IdOf<Book>; // never
```
- &は交差型で『A かつ B』と複数の型をひとつに結合させます。
- type NewUserIdではIdOfの型引数TはNewUserが参照されます。
- NewUserの型がUserと同じ型か拡張した型の場合はNewUserが持つidの値の型を抽出して、そうでない場合はnever型を抽出します。
- T['id']はインデックス演算子です。
- BookIdはUserと同じ型か拡張した型ではないためnever型が抽出されます。
#### Userを消してもstringが抽出できる理由
ここでは先ほどのコード例からtype NewUserに定義されていた`User & {id: string}` という交差型であった`User` を消しても同様の結果が得られています。
```typescript
type User = { id: unknown };
type NewUser = { id: string };
type IdOf<T> = T extends User ? T['id'] : never;
type NewUserId = IdOf<NewUser>; // string
```
- `T extends User` は`User` と同じもしくは拡張した型という意味で三項演算子で条件分岐をしています。
- `unknown` 型は全ての型のスーパータイプであり、`string` はサブタイプであるため、`unknown` は`string` になりえるので`true` となり、`T['id']`に処理が移ることで`type NewUserId` の結果値で`string` を得られています。
## inferキーワード
条件付き型における型のマッチングでは、そのマッチング中に取得した型を出力にも利用できます。
以下は配列の要素の型を抽出する`type Flatten<T>`を定義しています。
```typescript
type Flatten<T> = T extends Array<infer U> ? U : T;
const num = 5;
const arr = [3, 6, 9];
type A = Flatten<typeof arr>; //number
type N = Flatten<typeof num>; // 5
```
- constで変数numを宣言したので、numの型は数値リテラルの5です。
```typescript
type Flatten<T> = T extends Array<infer U> ? U : T;
let num = 5;
let arr = [3, "1", "1"];
type A = Flatten<typeof arr>; //number | string
type N = Flatten<typeof num>; //number
```
- `Array<infer U>` と`(infer U)[]` は同義です。
- `Array<number>` ` number[]`と書くと配列であり中身は`number` 型を指定できていたのを、受け取った値で型推論するために使われるのが`infer` です。
- 配列の中身を`number`と`string` にすると共用体型として型推論されます。
# 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.190~191](https://oukayuka.booth.pm/items/2368045)
# 2022年4月4日 りあクト! 第4章 TypeScriptで型をご安全に 4-5 条件付き型とテンプレートリテラル型(p.191~)
## テンプレートリテラル型
TypeScriptでは、テンプレートリテラルを型定義として扱うことが出来ます。
```typescript
type DateFormat = `${number}-${number}-${number}`;
const date1: DateFormat = '2020-12-05';
const date2: DateFormat = 'Dec.5,2020'; // compile error!
```
- 型エイリアスの定義時にテンプレートリテラルを使う事で型の定義ができます。
- number型をハイフン区切りで3つ羅列する型定義をしているため、それ以外ではコンパイルエラーになります。
#### 以下は少し応用した例です。
```typescript
const tables = ['users', 'posts', 'comments'] as const;
type Table = typeof tables[number];
type AllSelect = `SELECT * FROM ${Table}`;
type LimitSelect = `${AllSelect} LIMIT ${number}`;
const createQuery = (table: Table, limit?: number): AllSelect | LimitSelect =>
limit ? `SELECT * FROM ${table} LIMIT ${limit}` as const
: `SELECT * FROM ${table}` as const;
const query = createQuery('users', 20);
console.log(query);
```
- as constで配列の要素の型をリテラル型として認識できます。
- tables[number]で配列の要素を全て表し、typeofで型を共用体型で抽出したものにTableという型の参照名を付けます。(型エイリアス)
- AllSelectはテンプレートリテラルと共用体型を組み合わせた型です。
- createQueryにはTable型と、任意で数値を渡すことができます。
- 数値を第二引数で渡している場合は三項演算子がtrueとなりLimitSelect型 の値を返し、省略している場合はAllSelect型(LIMITなし)の値を返します。
出力結果: "SELECT * FROM users LIMIT 20"
#### 以下の例では、テンプレートリテラル型の中でinferを使用してます。
```typescript
const q1 = 'SELECT * FROM users';
const q2 = 'SELECT id, body, createdAt FROM posts';
const q3 = 'SELECT userId, postId FROM comments';
type PickTable<T extends string> = T extends `SELECT ${string} FROM ${infer U}` ? U : never;
type Tables = PickTable<typeof q1 | typeof q2 | typeof q3>;
```
- クエリ文字列から各クエリのテーブル名を抜き出すことができます。
共用体型と条件型を組み合わせると以下のような演算が行われるので、注意が必要です。条件型に対して分配法則が適用されています。
```typescript
T1 | T2 extends U ? A : B
// ↓
(T1 extends U ? A : B) | (T2 extends U ? A : B)
```
## 組み込みユーティリティ型
#### `Partial<T>`, `Required<T>`, `Readonly<T>`
`Partial<T>`, `Required<T>`, `Readonly<T>`をオブジェクト型に使うことで、一括でプロパティを省略可能にしたり、読み取り専用にすることができます。
```typescript
type Yano = {
skill: 'JS';
name: 'kohei';
}
// Yanoのプロパティを全て省略可能にする
type PartialYano = Partial<Yano>;
// => type PartialYano = {
// skill?: "JS" | undefined;
// name?: "kohei" | undefined;
// }
// Yanoのプロパティを全て必須にする
type RequiredYano = Required<Yano>;
// => type RequiredYano = {
// skill: 'JS';
// name: 'kohei';
// }
// Yanoのプロパティを全て読み取り専用にする
type ReadonlyYano = Readonly<Yano>;
// => type ReadonlyYano = {
// readonly skill: 'JS';
// readonly name: 'kohei';
// }
```
#### `Pick<T, K>`, `Omit<T, K>`
`Pick<T, K>`, `Omit<T, K>`をオブジェクト型に使うことで、オブジェクト型からプロパティを抽出したり、省くことができます。
```typescript
type Yano = {
skill: 'JS';
name: 'kohei';
ken: 'hyougo'
}
// Yanoのskillとnameのみを抽出した型を返す
type PickedYano = Pick<Yano, 'skill' | 'name'>;
// => type PickedYano = {
// skill: 'JS';
// name: 'kohei';
// }
// Yanoのkenプロパティを省いた型を返す
type OmittedYano = Omit<Yano, 'ken'>;
// => type OmittedYano = {
// skill: 'JS';
// name: 'kohei';
// }
```
#### `Extract<T, U>`, `Exclude<T, U>`
`ExtractM<T, U>`は、TからUの要素を抽出した型を返します。`Exclude<T, U>`は、TからUの要素を省いた型を返します。列挙的な型に対して使います。
```typescript
type Permission = 'r' | 'w' | 'x';
type RW1 = Extract<Permission, 'r' | 'w'>; // "r" | "w"
type RW2 = Exclude<Permission, 'x'>; // "r" | "w"
```
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.191~194](https://oukayuka.booth.pm/items/2368045)
# 2022年4月4日 りあクト! 第4章 TypeScriptで型をご安全に 4-5 組み込みユーティリティ型(p.195~)
## 続: 組み込みユーティリティ型
NonNullableは型引数に渡した型からnull undefinedを取り除いてくれます。
#### NonNullable\<T>
```typescript
type T1 = NonNullable<string | number | undefined>;
type T2 = NonNullable<number[] | null | undefined>;
const str:T1 = undefined; //compileerror!
const arr:T2 = null; // compile error!
```
#### Record\<K, T>
Recordは第一型引数に共用体型を指定、第二型引数に任意の型を指定することでオブジェクトの型が作成出来ます。
```typescript
type Animal = 'cat' | 'dog' | 'rabbit';
type AnimalNote = Record<Animal, string>;
const animalKanji: AnimalNote = {
cat: '猫',
dog: '犬',
rabbit: '兎',
};
```
#### Parameters\<T>、 ReturnType\<T>
Parameters\<T>は、関数の型を型引数に指定することで、その関数の引数の型を抽出し、タプル型で返します。
ReturnType\<T>は、関数の型を型引数に指定することで、その関数の戻り値の型を返します。
```typescript
const f1 = (a: number, b: string) => { console.log(a, b) };
const f2 = () => ({x: 'hello', y: true});
type P1 = Parameters<typeof f1>; // [a: number, b: string]
type P2 = Parameters<typeof f2>; // []
type R1 = ReturnType<typeof f1>; // void
type R2 = ReturnType<typeof f2>;
// type R2 = {
// x: string;
// y: boolean;
// }
```
## Uppercase\<T> Lowercase\<T> Capitaleze\<T> Upcapitalize\<T>
- Uppercase\<T> ...... Tの各要素の文字列をすべて大文字にします。
- Lowercase\<T> ...... Tの各要素の文字列をすべて小文字にします。
- Capitalize\<T> ...... Tの各要素の文字列の頭を大文字にします。
- Uncapitalize\<T> ...... Tの各要素の文字列の頭を小文字にします。
```typescript
type Company = 'Apple' | 'IBM' | 'GitHub';
type C1 = Lowercase<Company>; // 'apple' | 'ibm' | 'github'
type C2 = Uppercase<Company>; // 'IBM' | 'APPLE' | 'GITHUB'
type C3 = Uncapitalize<Company>; // 'apple' | 'iBm' | 'gitHub'
type C4 = Capitalize<C3>; // 'Apple' | 'IBM' | 'GitHub'
```
## 関数のオーバーロード
関数を多重定義することを、関数のオーバーロードと言います。オーバーロードで関数を定義するときに注意することは、オーバーロードで定義した全ての関数の型を満たすような関数を最後に定義することです。
```typescript
class Brooch {
pentagram = 'Silver Crystal';
}
type Compact = {
silverCrystal: boolean;
}
class CosmicCompact implements Compact {
silverCrystal = true;
cosmicPower = true;
}
class CrisisCompact implements Compact {
silverCrystal = true;
moonChalice = true;
}
function transform(): void;
function transform(item: Brooch): void;
function transform(item: Compact): void;
// 上のオーバーロードした関数の型を満たすような、
// 関数を定義する。
function transform(item?: Brooch | Compact): void {
if (item instanceof Brooch) {
console.log('Moon crystal power, make up!');
} else if (item instanceof CosmicCompact) {
console.log('Moon cosmic power, makeup!');
} else if (item instanceof CrisisCompact) {
console.log('Moon crisis, makeup!');
} else if (!item) {
console.log('Moon prisim power, makeup!');
} else {
console.log('Item is fake');
}
}
transform();
transform(new Brooch());
transform(new CosmicCompact());
transform(new CrisisCompact());
```
- 引数の型の指定はクラスだけではなく、`implements` 元のオブジェクトの型(インターフェース)でも指定できます。
- `implements` 元の`Conpact` を指定する事で、`CosmicCompact` と `CrisisCompact` を指定できています。
## オーバーロードの書き方での気づき
- 先に定義した関数を消して実際に実行する最後の関数のみでも実行できました。
- 先に定義した関数で型チェックができているので実行する関数の引数は(item?: any)でも実行できます。
## ポケモンを例にしてオーバーロードの例を書いてみました。
```typescript
class Mizu {
Ebui = 'Shawa-zu';
}
class Kaminari {
Ebui = 'Sanda-su'
}
class Honoo {
Ebui = 'Bu-suta-'
}
type Transform = {
(): void;
(item: Mizu): void;
(item: Kaminari): void;
(item: Honoo): void;
}
const transform: Transform = (item?) => {
if (item instanceof Mizu) {
console.log('シャワーズに進化した!!');
} else if (item instanceof Kaminari) {
console.log('サンダーズに進化した!!');
} else if (item instanceof Honoo) {
console.log('ブースターに進化した!!');
} else if (!item) {
console.log('イーブイやないかい');
} else {
console.log('にせものやないかい');
}
}
transform(); // "イーブイのままやないかい"
transform(new Mizu()); // "シャワーズに進化した!!"
transform(new Kaminari()); // "サンダースに進化した!!"
transform(new Honoo()); // "ブースターに進化した!!"
transform({ Ebui: 'Shawa-zu' });
```
出力結果
▶"イーブイのままやないかい"
▶"シャワーズに進化した!!"
▶"サンダースに進化した!!"
▶"ブースターに進化した!!"
▶"にせものやないかい"
- 5つ目の関数の実行の例から、クラスのインスタンスではないものを渡しても構造が同じ型を渡すとTypeScript上では型チェックは通ることがわかります。
(TypeScriptの型の互換性の判定が、その名前ではなく構造的サブタイピングによって行われているためです。)
- JavaScriptのinstance ofでは真にならないため型チェックだけではなくインスタンスのチェックの処理を書くことで堅牢性のあるコードにしています。また、そうすべきです。
#### 構造的サブタイピング(構造的部分型)をひとことで言うと
- インターフェースやクラスの型ではなくそのオブジェクトが持っているプロパティの型が等しいかで型チェックが行われる仕組みです。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.195](https://oukayuka.booth.pm/items/2368045)
[オーバーロード関数 (overload functions)](https://typescriptbook.jp/reference/functions/overload-functions)
# 2022年4月7日 りあクト! 第4章 型アサーションと型ガード asによる型アサーション (p.201~)
## 型アサーションとは
開発者が型を断定することを、型アサーションと言います。型を断定することによって、コンパイラによる型チェックを強引に通すことができます。しかし、断定した型でコードが実行されるので、メソッドが呼び出せない等、予期せぬエラーが発生します。
```typescript
type User = { username: string; address: { zipcode: string; town: string } };
const str = `{ "username": "patty", "town": "Maple Town" }`;
const data: unknown = JSON.parse(str);
const user = data as User;
console.log(user.address.town); // TypeError: Cannot read property 'town' of undefined
```
- 型エイリアス type User という型の参照を定義します。
- JSONデータをunknown型で受け付けます。
- `T as (U extends T)` または `(T extends U) as U`であるときに、asの記述ができます。
- dataがUser型ということを断定して変数userに代入します。
- addressが存在しないため、タイプエラーとなります。つまり、型の安全性が保証されていないということです。
型アサーションはあくまでコンパイラによる型の解釈が変わるだけであり、実際の値が変化しているわけではありません。
## 型キャスト
異なるデータ型の値を任意の型にコンバートするものです。
```typescript
const n = 123;
const s1 = String(n);
console.log(s1); // "123"
console.log(typeof s1); // string
const s2 = n as string; // nを文字列型として断定しようとしている
```
- 数値をStringコンストラクタで文字列に変換しています。
- 最後の行はnを文字列型として断定しようとしていますがエラーが発生します。
- 型アサーションはスーパータイプ、サブタイプの関係性がないと使えません。
#### 二重アサーション
unkonwn型を挟む形で二重アサーションを行えばコンパイルは通ってしまいますが、推奨されていません。
型アサーションの抜け道 ーーーーーーーーーーーーーーーーー
```typescript
const str = (123 as unknown) as string; // unknownはスーパータイプ
str.split(',');
str.split(',');
// エラー ▶ Uncaught TypeError: n.split is not a function
```
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.201](https://oukayuka.booth.pm/items/2368045)
# 2022年4月8日 りあクト! 第4章 型アサーションと型ガード asによる型アサーション (p.203~)
## 型ガード
if文の条件部分で`typeof`や`instanceof`などを用いて型を判定して、スコープ内で型を保証することを型ガードと言います。
```typescript
const foo: unknown = '1,2,3,4';
if (typeof foo === 'string') {
console.log(foo.split(','));
}
consolo.log(foo.split(',')); // compile error!
```
- typeof で型ガードを行なっています。
- typeofによってstring型だと判断されたブロック内では、変数fooに stringのプロトタイプメソッドである split()が使えています。
#### instanceofによる型ガード
```typescript
class Base { common = 'common'; }
class Foo extends Base { foo = () => { console.log('foo');}}
class Bar extends Base { bar = () => { console.log('bar');}}
const doDivide = (arg: Foo | Bar) => {
if (arg instanceof Foo) {
arg.foo();
arg.bar(); // comopie error!
} else {
arg.bar();
arg.foo(); // compile error!
}
console.log(arg.common);
}
doDivide(new Foo());
doDivide(new Bar());
```
- instanceof Fooのブロック内ではfooクラスの型に絞り込まれていることがわかります。
## 型述語
この関数がtrueを返す場合に引数argの型がUserであることがコンパイラに示唆されます。
```typescript
type User = { username: string; address: { zipcode: string; town: string}};
const isUser = (arg: unknown): arg is User => {
const u = arg as User;
return (
typeof u?.username === 'string' &&
typeof u?.address?.zipcode === 'string' &&
typeof u?.address?.town === 'string'
);
};
const u1: unknown = JSON.parse('{}');
const u2: unknown = JSON.parse('{"username": "patty", "address": "Maple Town" }');
const u3: unknown = JSON.parse('{"username": "patty", "address": { "zipcode": "111", "town": "Maple Town" }}',
);
[u1, u2, u3].forEach((u) => {
if (isUser(u)) {
console.log(`${u.username} lives in ${u.address.town}`);
} else {
console.log("It is not User");
console.log(`${u.username} lives in ${u.address.town}`); // compile error!
}
});
```
- isUserでは型アサーションで引数をUser型に断定することでreturn文の中でプロパティにアクセスすることができています。
- isUserは真偽値を返しますが、返り値の型をコンパイラに伝えることが出来ます。
## 型ガードのわかりやすい例
ユーザー定義型ガードを適用させる前
```typescript
const isString = (a: unknown): boolean => {
return typeof a === 'string';
};
// 型の絞り込みは、呼び出し元のスコープには引き継がれない
// TypeScriptが理解しているのは、isStringがbooleanを返したということだけです。
const parseInput = (input: string | number) => {
let formattedInput: string
if(isString(input)) {
formattedInput = input.toUpperCase()
}
}
```
ユーザー定義型ガード
```typescript
// Type predicateの宣言は、戻り値がboolean型の関数に対して適用できる
const isString = (a: unknown): a is string => {
return typeof a === 'string';
};
// 呼び出し元のスコープが型ガードの内容を引き継ぐことができる
const parseInput = (input: string | number) => {
let formattedInput: string
if(isString(input)) {
formattedInput = input.toUpperCase()
}
}
```
- 型ガードを使わないと型自体は呼び出し元のスコープに引き継がれません。
- `a is string` とする事で`isString` の返り値に`string` 型を持たせられています。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.203](https://oukayuka.booth.pm/items/2368045)
# 2022年4月9日 りあクト! 第4章 型アサーションと型ガード asによる型アサーション (p.203~)
## 復習
in演算子は、指定したオブジェクト上に指定したプロパティがあるかを判定できます。
```
"プロパティ名" in オブジェクト; // true or false
```
###### 型アサーション
型を断定するものです。`as 断定する型`とします。
コンパイラによる型の解釈を変えているだけで、実際の値が変化しているわけではありません。
**問題点**
以下のケースでは、無理やりコンパイラが通っており、実行時にエラーになっています。つまり、型の安全性が保証されていません。
```typescript
type User = { username: string; address: { zipcode: string; town: string } };
const str:unknown = { username: "patty", town: "Maple Town" };
const user = str as User;
console.log(user.address.town); // TypeError: Cannot read property 'town' of undefined
```
型アサーションはスーパータイプ、サブタイプの関係性がないと使用することが出来ません。
型ガードを使うことで、任意の型に絞って型安全性を保ちながら型を割り出すことができます。
###### 型ガード
- 型ガードの種類
- typeof・・・プリミティブな値の型を取得し条件式でコンパイラに伝え型を保証します。
- instanceof・・・クラスのインスタンスか真偽値を取得し条件式でコンパイラに伝え型を保証します。
- ユーザー定義型ガード・・・型述語(関数の戻り値の型を`引数名 is 型`とする)を使った関数などを自身で作って型を絞り込むしくみを作ります。
## 型アサーションと型ガードの違い
**型アサーション**
中身の型がわからないものの型を断定する事で、その値のプロパティやメソッドにアクセスできるが、型定義にあって実際のプロパティやメソッドにないものにアクセスするとコンパイルエラーにならずに実行時にエラーになるため型安全が保証されていません。
```typescript
type User = { username: string; address: {zipcode: string; town: string} };
const str = `{"username": "patty", "town": "Maple Town"}`;
const data: unknown = JSON.parse(str);
const user = data as User;
// TypeScriptのコンパイルエラーが出ない。
// TypeScriptのコンパイル時にはエラーが出ず、実行時にエラーが出る。
console.log(user.address.town); // => Cannot read properties of undefined (reading 'town')
```
**型ガード**
中身の型がわからないものをif文で型を絞り込むため、型安全が保証された上でプロパティにアクセスできます。
以下はtypeofを使った例です。
```typescript
const foo: unknown = '1,2,3,4';
if (typeof foo === 'string') {
console.log(foo.split(','));
}
consolo.log(foo.split(',')); // compile error!
```
- typeof で型ガードを行なっています。
- typeofによってstring型だと判断されたブロック内では、変数fooに stringのプロトタイプメソッドである split()が使えています。
## 本章
#### カリー化
カリー化の説明は「りあクト! 【Ⅰ. 言語・環境編】p.136」に記述されている為、以下に引用します。
> 複数の引数を取る関数を、引数が「元の関数の最初の引数」で戻り値が「引数として元の関数の残りの引数を取り、それを使って結果を返す関数」である高階関数にすることを「カリー化」と呼ぶ。
```typescript
// JavaScript
const add = a => b => a + b;
// TypeScript
// TypeScriptでは、関数の引数に型アノテーションをつける必要がある
const add = (a: number) => (b: number) => a + b;
console.log(add(1)(2)); // 3
```
#### 型ガード (応用)
```typescript
type Result<T, E extends Error> = Ok<T, E> | Err<T, E>;
export class Ok<T, E extends Error> {
constructor(readonly val: T) {}
isOk = (): this is Ok<T, E> => true;
isErr = (): this is Err<T, E> => false;
}
export class Err<T, E extends Error> {
constructor(readonly err: E) {}
isOk = (): this is Ok<T, E> => false;
isErr = (): this is Err<T, E> => true;
}
export const withResult = <T, A extends any[], E extends Error>(
fn: (...args: A) => Promise<T>,
) => async (...args: A): Promise<Result<T, E>> => {
try {
return new Ok(await fn(...args));
} catch (error) {
if (error instanceof Error) {
return new Err(error as E);
}
}
};
```
- このコードに関しては全体を読み解きながら復習のための議論をしましたが、ブログにアウトプットする内容にはならなかったため、コードの掲載のみにとどめます。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.206](https://oukayuka.booth.pm/items/2368045)
[サルでもわかるカリー化とそのメリット](https://kazchimo.com/2021/03/29/monkey_curry/)
# 2022年4月11日 りあクト! 第4章 モジュールと型定義 (p.207~)
## TypeScriptのimport/export
JavaScriptとほぼ同じで異なる点は2つあります。
- TypeScriptのimportは拡張子を書くとエラーになる
- 同名の型を扱った場合の挙動
#### importで拡張子を書くとエラーになる理由
モジュールを解決する際に独自のルールで探索するからです。
独自の探索の7パターン
1. src/bar.ts
2. src/bar.tsx
3. src/bar.d.ts
4. src/bar/package.json の types または typings プロパティで設定されている型定義ファイル
5. src/bar.index.ts
6. src/bar.index.tsx
7. src/bar/index.d.ts
上から探索していって最初に見つかったものが読み込まれ、最後までヒットしなかった場合はその時点でエラーになります。
<span style="color: red; font-weight: bold;">TypeScriptでのimportは拡張子はつけない!と覚えておきましょう。</span>
※`.d.ts` の型定義ファイルの説明は後に説明があるようです。
#### 同名の型を扱った場合の挙動
TypeScriptでは、インターフェースや型エイリアスもimportと exportの対象になります。
以下のコードは、type, interface, 関数をそれぞれエクスポート出来ることを示唆しています。
```typescript
type Species = 'rabbit' | 'bear' | 'fox' | 'dog';
interface Resident {
name: string;
age: number;
species: Species;
}
const isCanine = (resident: Resident): boolean =>
['dog', 'fox'].includes(resident.species);
export { Species, Resident, isCanine };
```
「りあクト! 【Ⅰ. 言語・環境編】p.209、引用です。
> TypeScriptでは、同じ名前空間の中に『**変数宣言空間**(Variable Declaration Space)』と『**型宣言空間**(Type Declaration Space)』という 2 つの宣言空間が存在していて、名前の管理が別々になっている
上記から変数宣言と型宣言は同名の変数と型は共存出来ることがわかります。
export、importをした場合、以下のようになります。
##### module/currency-export.ts
```typescript
const rate: { [unit: string]: number } = {
USD: 1,
EUR: 0.9,
JPY: 108,
GBP: 0.8,
};
type Unit = keyof typeof rate;
type Currency = {
unit: Unit;
amount: number;
};
const Currency = {
exchange: (currency: Currency, unit: Unit): Currency => {
const amount = currency.amount / rate[currency.unit] * rate[unit];
return { unit, amount };
},
};
export { Currency };
```
##### module/currency-import.ts
```typescript
import { Currency } from './currency-export';
const dollars: Currency = {
unit: 'USD',
amount: 100,
};
console.log(dollars);
console.log(Currency.exchange(dollars, 'JPY'));
```
ターミナル
```
cd 04-typescript/05-advanced/module/
ts-node currency-import.ts
{ unit: "USD", amount: 100 }
{ unit: "JPY", amount: 10800 }
```
- 一つのimport文で型エイリアスとオブジェクトの`Currency` が使えています。
- 同じ名前空間の中に変数宣言空間と型宣言空間が存在しているので同じ名前が持たせられています。
- コンビネーションと呼びます。
## 型のみのimportとexport
import/export時に`type` をつけると型のみで行えます。
```typescript
type Species = 'rabbit' | 'bear' | 'fox' | 'dog';
class Resident {
name = '';
age=0;
species: Species | null = null;
}
export type { Species, Resident };
```
- クラスは宣言時にインスタンスのインターフェース型とコンストラクタ関数の宣言を同じ名前で同時に行っています。
- この場合、Residentクラスは型のみをエクスポートしています。
- import先ではクラスの機能を使うことが出来ないため、new演算子を使うとコンパイルエラーになります。
```typescript
import type { Foo } from 'bar'
```
- Fooはクラスです。
- コンストラクタはimportされないのでnewは使えません。
- クラスをインターフェースとして扱う場合の型がimportされます。
内部の挙動として型のみのimport/exportはTypeScriptの中だけで完結しコンパイル後のJavaScriptに残らないので、パフォーマンスで多少有利な可能性があるようです。
コードを読み取りやすくする目的でも使用される事もあると書籍で書かれていました。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.207](https://oukayuka.booth.pm/items/2368045)
# 2022年4月12日 りあクト! 第4章 JavaScriptモジュールを TypeScript から読み込む(p.212~)
# JavaScript モジュールを TypeScript から読み込む
npmのリポジトリで提供されている多くのパッケージは、TypeScriptで書かれているものでも、TypeScriptのままで配布されているものはあまりありません。
(理由は以下)
- JavaScript環境との相互運用が簡単である。
- .tsファイルが配布パッケージの中にあるとコンパイラがそれを見つけてアプリと一緒に毎回コンパイルしてしまう。
JavaScriptにコンパイル済みのファイルと、『宣言ファイル(Declaration File)』 というTypeScriptの型情報を定義したファイルをパッケージングして配布されることが一般的です。
## TypeScriptから読み込めるようにJavaScriptのモジュールを作る
##### tsファイルから型情報が付加されたjsモジュールを作成する流れ
- sailrmoon-transform.tsにエクスポート文を記述します。
- tsc -d コマンドでエクスポートするファイルをjsファイルにコンパイルすると同時にd.tsという型定義ファイルを生成します。(コンパイルしたjsファイルと型定義のd.tsファイルの二つが生成されます)
- 元のtsファイルを削除します。
- 任意のtsファイルでコンパイル後のjsファイルをインポートすると、d.tsファイルも同名のファイル名という事で関連づけられているため、型定義も反映されます。
#### declare
TypeScriptからJavaScriptモジュールをただインポートすると、実装だけがあって型がない状態になります。
そこでTypeScriptのコンパイラに、こういう変数とか関数がこういう型で存在していることを伝えて、宣言空間にそれらを定義するための構文がdeclareです。
今回書籍での例で生成されるd.tsファイル
```typescript
declare class Brooch {
pentagram: string;
}
declare type Compact = {
silverCrystal: boolean;
};
declare class CosmicCompact implements Compact {
silverCrystal: boolean;
}
declare class CrisisCompact implements Compact {
silverCrystal: boolean;
moonChalice: boolean;
}
declare function transform(); void;
declare function transform(item: Brooch): void;
declare function transform(item: Compact): void;
export { transform, Brooch, CosmicCompact, CrisisCompact };
```
- こうした既存のJavaScriptモジュールに型情報を付加する宣言のことを**アンビエント宣言**といいます。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.212](https://oukayuka.booth.pm/items/2368045)
[TypeScriptを始める – tscコマンド](https://mae.chab.in/archives/2525)
[【TypeScript】型定義ファイル(.d.ts)ファイルについて](https://zenn.dev/nash/articles/bb5048a2754245)
# 2022年4月12日 りあクト! 第4章 型定義ファイルはどのように探索されるか(p.215~)
npmのパッケージに含まれている型定義ファイルをプロジェクトに関連付ける方法は大きく分けて2つあります。
## 1. JavaScriptファイルと同じ階層に同じ名前で.d.ts拡張子の型定義ファイルを置く
例を挙げる『ky』というライブラリは、パッケージ内で同じ階層に同名のjsファイルと.d.tsファイルがあるため、型定義も読み込まれます。
https://github.com/sindresorhus/ky
##### node_modules/ky
```
├─ index.d.ts
├─ index.js
├─ license
├─ package.json
├─ readme.md
├─ umd.d.ts
└─ umd.js
```
## 2. パッケージ配下のpackage.jsonに"types"または"typings"プロパティで型定義ファイルのパスを指定する
例を挙げると『Immer』というイミュータブルなオブジェクトツリーを扱うためのライブラリがこの形式を取っています。
https://github.com/immerjs/immer/blob/master/package.json
##### package.json
```
{
"name": "immer",
"version": "9.0.0-beta.1",
"description": "Create your next immutable state by mutating the current one",
"main": "dist/index.js",
"module": "dist/immer.esm.mjs",
"exports": {
".": {
"import": "./dist/immer.esm.mjs",
"require": "./dist/index.js"
},
"./*": "./*"
},
"umd:main": "dist/immer.umd.production.min.js",
"unpkg": "dist/immer.umd.production.min.js",
"jsdelivr": "dist/immer.umd.production.min.js",
"jsnext:main": "dist/immer.esm.mjs",
"react-native": "dist/immer.esm.mjs",
"source": "src/immer.ts",
"types": "./dist/immer.d.ts",
.
.
```
- typesプロパティに型定義ファイルをパス付きで設定してあります。
- typingsというプロパティ名でも可能です。
- パッケージのルートディレクトリに`index.d.ts` という名前で配置すればこの設定は省略可能です。(多くの場合、このファイルがある上でpackage.jsonの設定も省略されていないようです)
## 公式型定義ファイルが配布されていない場合
有志の第三者の方が作ってくれていることが多く、まとめて公開されている『Definitely Typed』があります。
https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types
上記の大元のリポジトリから直接、型定義ファイルを探すのではなく、いくつかの検索方法があります。
目当ての型定義ファイルが決まっていて検索する方法
- 『TypeSearch』で検索をする方法
- https://www.typescriptlang.org/dt/search?search=
- yarn info @types/任意のファイル名で検索する方法
- npmで検索する方法
TypeScriptがnode_modules/@typesディレクトリから(型定義ファイルがインストールされている前提)自動で検索し対象のパッケージと関連付けしてくれます。
## 第三者の方が作ってくれた型定義ファイルも存在しない場合
src/ディレクトリに任意の名前で`.d.ts` ファイルを配置します。 今回は`src/types.d.ts` というファイルでフォーマットを作ります。
```typescript
declare module awesomelib {
export type Amazing = { ... };
declare function fabulous(arg: Amazing): void;
.
.
export default fabulous;
}
```
- `import fabulours from 'awesomelib'` でパッケージがインポートされるとこの型定義が適用されます。
### 型定義ファイルの優先順位
型定義ファイルには優先順位が存在します。以下の順番
で型定義が適用されます。
1. ローカルでの型宣言
2. モジュールがパッケージ内に持っている型ファイル
3. node_modules/@types配下の型ファイル
## 復習
#### ビルドとコンパイルの違い
**コンパイル**とは、"ソースコードをコンピュータが認識できるオブジェクトコードに変換すること"です。
**ビルド**とは、"コンパイルしたファイルを一つにまとめ、実際に実行まですること"です。
#### node_modules
package.jsonを元にしてインストールされた各種パッケージが配置されるディレクトリを指します。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.215~](https://oukayuka.booth.pm/items/2368045)
[コンパイルとビルドの違いを理解しよう](https://qiita.com/tomokichi_ruby/items/e1d52d3f34877389f905)
[この話において、それぞれのソースコードを機械語に翻訳する作業がコンパイルです。](https://wa3.i-3-i.info/diff502programming.html#:~:text=%E3%81%93%E3%81%AE%E8%A9%B1%E3%81%AB%E3%81%8A%E3%81%84%E3%81%A6%E3%80%81%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E3%81%AE,%E4%BD%9C%E3%82%8A%E4%B8%8A%E3%81%92%E3%82%8B%E4%BD%9C%E6%A5%AD%E3%81%8C%E3%83%93%E3%83%AB%E3%83%89%E3%81%A7%E3%81%99%E3%80%82)
# 2022年4月15日 りあクト! 第4章 TypeScriptの環境設定(p218~)
## TypeScriptのコンパイルオプション
#### tsconfig.json
TypeScriptプロジェクトのコンパイラ設定を保存しておくためのファイルです。
コンパイルが実行される際、デフォルトではプロジェクトルート配下から順に探索し、最初に見つかったtsconfig.json ファイルが読み込まれて記述されている設定がコンパイラオプションとして有効になります。
#### config/tsconfig.json
```typescript
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true, // 複数のオプションがまとめて有効になる設定
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
```
tsconfig.jsonはcreate-react-appコマンドでtypescriptを指定してプロジェクトを作成した際に生成される設定ファイルです。上記はデフォルトの内容です。
### strictプロパティには以下の設定が含まれています
#### noImplicitAny
暗黙的にanyが指定されている式や宣言があればエラーになる
#### noImplicitThis
thisが暗黙的にanyを表現していればエラーになる
#### alwaysStrict
すべてのソースファイルの先頭に 'use strict' が記述されているものとみなし、ECMAScriptのstrictモードでパースする
#### strictBindCallApply
bind()、call()、apply()メソッド使用時に、その関数に渡される引数の型チェックを行う
#### strictNullChecks
他のすべての型から null および undefined が代入不可になる
#### strictFunctionTypes
関数の引数の型チェックが「共変的(Bivariant)」ではなく、「反変的
(Contravariant)」 に行われるようになる
#### strictPropertyInitialization
宣言だけで初期化されないクラスプロパティ(=メンバー変数)があるとエラーになる(※ strictNullChecks も併せて有効にしておく必要あり)
## tsconfig.jsonのカスタマイズ
**オプションの説明の続きです。**
#### target
コンパイル先のJavaScriptのバージョンを指定するものです。
#### esnext
コンパイルに使われるバージョンのTypeScriptがサポートしているECMAScriptの最も新しいバージョンを示します。
#### lib
コンパイルに含めるライブラリを指定します。
ライブラリdomとdom.iterableはDOM操作を行うためのライブラリです。
esnextは最新のEXMAScript構文をサポートするライブラリです。
#### module
コンパイル後のモジュール構文をどのモジュールシステム形式にするかを設定します。
動作環境がサーバサイドのNode.jsであれば、commonjsを指定します。
#### noEmit
trueにするとコンパイル結果を出力しなくなります。
現行のCRAによるプロジェクト設定では tsc は構文チェックしか行わず、実際の TypeScriptのコンパイルはBabelが行ってるためです。
> @babel/preset-typescript というプリセット(プラグインを特定のカテゴリーに よってまとめたもの)で TypeScript のコードから型情報を除去して ES2015 相当のコードに変換して、さらに @babel/preset-envで ES5 にコンパイルしてる
##### tscで型チェック・Babelでコンパイルを行う理由
tscでコンパイルを行うと、Promise等の組み込みオブジェクトをコンパイルできません。そして、babelのみでコンパイルを行うと、型チェックが行われません。tscで型チェック・Babelでコンパイルをすることによって、型チェックを行いつつコンパイルすることができます。
#### jsx
tsxファイルをjsxやjsにコンパイルする際の出力の形式を指定します。
react-jsxという設定は、TypeScript 4.1 から導入されたオプションで、React 17.0 以降の新しいJSX変換形式に対応するものです。
#### include
コンパイル対象となるファイルを指定するためのものです。
デフォルトではsrcが設定されているため、ルート直下のsrc/ディレクトリにTypeScriptファイルを置くことでコンパイルが適用されます。
もしsrcディレクトリ以外にtsファイルを配置するのならばこの設定に追加する必要があります。
## カスタマイズ(デフォルトに加えて追加)した設定
#### baseurl
モジュールのインポートのパス指定に絶対パスを使えるようにし、起点となるディレクトリを指定するオプションです。
設定前
```
import MenberList from '../../organisms/MenberList';
```
設定後
```
import MenberList from 'components/organisms/MenberList';
```
TypeScriptの公式ドキュメントでは、非相対インポートと書かれているが、開発者の間では絶対インポートと呼ばれています。
この設定はVSCodeと相性が悪い可能性があり、新規作成したファイルでは絶対パス指定を認識してくれないことがあります。
解消方法
- shift+command+PまたはF1キーでコマンドパレットを開き >typeを入力するとリストアップされるTypeScript: Reload Projectを選択・実行する
- コンソールから`touch tsconfig.json` を実行する
#### downlevelIteration
コンパイルターゲットが ES5 以前 に設定されている場合でも、ES2015 から導入された各種イテレータ周りの記述(for...ofでindexの値も同時に取得したい場合やイテレータのスプレッド構文による展開)に対応させる設定です。
## 参考
[りあクト! 【Ⅰ. 言語・環境編】 p.218~](https://oukayuka.booth.pm/items/2368045)
[tscとBabel](https://t-yng.jp/post/tsc-and-babel)