diff --git a/docs/4-advanced/03-typescript/index.mdx b/docs/4-advanced/03-typescript/index.mdx
index 49f83951..efeab7c8 100644
--- a/docs/4-advanced/03-typescript/index.mdx
+++ b/docs/4-advanced/03-typescript/index.mdx
@@ -32,16 +32,16 @@ function formatDate(date: Date) {
}
```
-これにより、次のような開発者体験が得られます。
+TypeScriptを導入することにより、このプログラムを記述する際に、次のような支援が得られます。
- `date.`と入力されたタイミングで、使用可能なメソッドが全て表示されます
- 誤った型の引数 (動画内では文字列) を指定すると、エラーが表示されるようになります
-:::tip[静的型言語との比較]
+:::tip[他言語との比較]
-C++やJavaなどの一般的なプログラミング言語では、型の情報は実行に何らかの影響を与えますが、TypeScriptはJavaScriptにトランスパイルされる言語であり、実行時には型の情報は一切利用されません。
+C++やJavaなどのプログラミング言語では、型の情報は実行に何らかの影響を与えますが、TypeScriptはJavaScriptにトランスパイルされる言語であり、実行時には型の情報は一切利用されません。
:::
@@ -78,13 +78,13 @@ npx tsc main.ts
すると、同名のJavaScriptファイルが生成されます。このファイルを実行すれば、通常のJavaScriptとして実行できます。
-## TypeScriptの基礎
+なお、TypeScriptのウェブサイトで提供されている[TS Playground](https://www.typescriptlang.org/play)を用いると、ブラウザ上でTypeScriptのコードを記述し、型チェックの結果を確認できます。
-TypeScriptを試すには、Microsoftが提供している[TS Playground](https://www.typescriptlang.org/play)を用いると便利です。必要に応じて利用してください。
+## TypeScriptのデータ型
-### 型を記述できる場所
+TypeScriptにおけるデータ型とは、その型の値として扱うことのできる全ての**値の集合**です。例えば、`number`型の集合には、`0`や`3.14`、`-7`など、数値の値が含まれます。
-TypeScriptの型は、関数の引数や戻り値、変数の後に`:`とともに記述できます。
+型は、変数や関数の引数など、**その場所に入る値の種類を明示したい箇所**に、`:` (コロン) に続けて記述します。
```typescript
// addはnumber型の引数a, bをとりnumber型の値を返す関数
@@ -96,7 +96,7 @@ function add(a: number, b: number): number {
let sum: number = add(3, 4);
```
-データ型が誤っている場合、TypeScriptはエラーを出力します。
+変数、関数の引数、戻り値など、**値の型が定まっている位置に異なる型の値が入る可能性がある**プログラムを検出すると、TypeScriptはエラーを報告します。例えば、次のプログラムにおいて、`"7"`や`"3"`、`"4"`は`number`型に属さないため、エラーが出力されます。
```typescript
sum = "7"; // Type 'string' is not assignable to type 'number'.
@@ -104,22 +104,72 @@ sum = "7"; // Type 'string' is not assignable to type 'number'.
add("3", "4"); // Argument of type 'string' is not assignable to parameter of type 'number'.
```
-### データ型と値
+TypeScriptでは、`number`型のほかにも、次の表に示すように、さまざまなデータ型が用意されています。
-TypeScriptのデータ型は、**全ての値を含む集合`unknown`の部分集合**になります。ある値`v`が集合`T`に属するとき、`v`は`T`型であるといいます。例えば、数値`1`は`1`型、`number`型、`unknown`型のいずれにも当てはまります。なお、空集合は`never`型です。
+| 型名 | 説明 | 例 |
+| --------- | ------------ | ------------------------- |
+| `number` | 数値 | `0`, `3.14`, `-7` |
+| `string` | 文字列 | `"Hello"`, `"TypeScript"` |
+| `boolean` | 真偽値 | `true`, `false` |
+| `object` | オブジェクト | `{ name: "田中" }` |
+
+### 確認問題
+
+JavaScriptで記載された次の関数`repeat`に対し、TypeScriptとして適切と思われる型を追記してください。
```typescript
-// すべて正しい
-const a: unknown = 1;
-const b: number = 1;
-const c: 1 = 1; // 左辺の1はデータ型 (unknownの部分集合) としての1
+function repeat(text, times) {
+ let result = "";
+ for (let i = 0; i < times; i++) {
+ result += text;
+ }
+ return result;
+}
+```
+
+
+
+関数`repeat`は、文字列`text`を`times`回繰り返して結合した新しい文字列を返す関数です。したがって、`text`は`string`型、`times`は`number`型、戻り値も`string`型とするのが適切です。
-// never型にはどんな値も代入できない
-// const d: never = 1;
+```typescript
+function repeat(text: string, times: number): string {
+ let result: string = "";
+ for (let i: number = 0; i < times; i++) {
+ result += text;
+ }
+ return result;
+}
```
+
+
+## 部分型の関係
+
+TypeScriptの型には包含関係があり、`T`が`U`の部分集合であるとき、`T`は`U`の**部分型**であるといいます。
+
+この部分型関係は、**全体集合を`unknown`型、空集合を`never`型**として、次の図のように整理できます。
+

+例えば、次の例のように、数値`1`は`1`型、`number`型、`unknown`型のいずれにも属します。
+
+```typescript
+const a: unknown = 1;
+const b: number = 1;
+const c: 1 = 1; // 左辺の1はデータ型としての1
+```
+
+部分型の値は、より大きな型の値として扱うことができます。例えば、次の例では、`string`型の変数`value`を、`unknown`型の引数を受け取る関数`logValue`に渡しています。`string`型は`unknown`型の部分型であるため、TypeScriptはこのプログラムを正しいとみなします。
+
+```typescript
+function logValue(value: unknown) {
+ console.log(value);
+}
+
+const value: string = "Hello";
+logValue(value); // string型はunknown型の部分型
+```
+
:::tip[`any`型]
TypeScriptの標準設定では、型が判明しなかった場合、`any`型が指定されたものとみなされます。`any`型の値には、どんな操作でも許容されます。`any`型の値はどんな型の変数にも代入できますし、`any`型の変数にはどんな値でも代入できます。上の集合のどの部分にも当てはまりません。
@@ -133,7 +183,7 @@ strangeValue.strangeMethod();
:::
-### データ型の別名
+## データ型の別名
`type`宣言を用いると、データ型に対して別名を付けられます。
@@ -150,21 +200,23 @@ const age: Age = 18;
:::
-### オブジェクト型
+## オブジェクト型
オブジェクト型では、プロパティの名前や、値の型が指定できます。
```typescript
-// Studentはstring型のnameプロパティとnumber型のageプロパティを持つオブジェクト
-type Student = {
+// Personはstring型のnameプロパティとnumber型のageプロパティを持つオブジェクト
+type Person = {
name: string;
age: number;
};
-let student: Student = { name: "田中", age: 18 };
+let person: Person = { name: "田中", age: 18 };
```
-なお、余分なプロパティを持つオブジェクトでも問題なく代入できます。次の例から、`Teacher`は`Student`の部分集合であることが分かります。
+TypeScriptでは、**プロパティが多いオブジェクト型は、プロパティが少ないオブジェクト型の部分型**とみなされます。
+
+次の例では、プロパティの数が多い`Teacher`型のオブジェクトを、プロパティの数が少ない`Person`型の変数に代入しています。これは、`Teacher`型のオブジェクトは`Person`型のオブジェクトの全てのプロパティを持っており、`Person`型のオブジェクトに対する全ての操作を安全に実行できるためです。
```typescript
type Teacher = {
@@ -174,24 +226,97 @@ type Teacher = {
};
let teacher: Teacher = { name: "鈴木", age: 18, subject: "数学" };
-student = teacher;
+person = teacher;
-// Property 'subject' is missing in type 'Student' but required in type 'Teacher'.
-teacher = student;
+// Property 'subject' is missing in type 'Person' but required in type 'Teacher'.
+teacher = person;
```
-### 配列型
+:::note[クラス]
-型`T`の配列型は、`T[]`のように記述できます。また、`T`が`U`の部分集合であれば、`T[]`は`U[]`の部分集合になります。
+クラス名は、そのまま型名として利用できます。また、フィールドにも型を指定できます。
+
+```typescript
+class Person {
+ name: string;
+ age: number;
+
+ constructor(name: string, age: number) {
+ this.name = name;
+ this.age = age;
+ }
+}
+
+const person: Person = new Person("田中", 18);
+```
+
+:::
+
+### 確認問題
+
+`Product`型と`Book`型が次のように定義されているとします。
+
+```typescript
+type Product = {
+ name: string;
+ price: number;
+};
+
+type Book = {
+ name: string;
+ price: number;
+ author: string;
+};
+```
+
+次のプログラムに対し、TypeScriptとして適切と思われる型を追記してください。
+
+```javascript
+function calculateTotal(item, quantity) {
+ return item.price * quantity;
+}
+
+const book = {
+ name: "TypeScript入門",
+ price: 2500,
+ author: "山田 太郎",
+};
+const total = calculateTotal(book, 3);
+```
+
+
+
+- `calculateTotal`関数の引数`item`は`Product`型と`Book`型のいずれも文法的には正しいですが、関数の処理において`Book`型特有のプロパティを使用していないため、`Product`型を指定するのがより適切です。
+- `book`は`Book`型のオブジェクトです。
+- `quantity`は商品の数量を表すため`number`型とします。戻り値も合計金額を表すため`number`型とします。
+
+```typescript
+function calculateTotal(item: Product, quantity: number): number {
+ return item.price * quantity;
+}
+
+const book: Book = {
+ name: "TypeScript入門",
+ price: 2500,
+ author: "山田 太郎",
+};
+const total: number = calculateTotal(book, 3);
+```
+
+
+
+## 配列型
+
+型`T`の配列型は、`T[]`のように記述できます。また、`T`が`U`の部分型であれば、`T[]`は`U[]`の部分型になります。
```typescript
const numbers: number[] = [1, 2, 3];
-// number[]はunknown[]の部分集合
+// number[]はunknown[]の部分型
const unknowns: unknown[] = numbers;
```
-### 関数型
+## 関数型
関数型では、引数や戻り値の型が指定できます。引数名は異なっていても同じ型だとみなされます。
@@ -206,18 +331,17 @@ function add(a: number, b: number): number {
const operator: BinaryNumberOperator = add;
```
-引数の数が少ない関数型は、多い関数型の部分集合とみなされます。
+:::tip[関数型の部分型の関係]
-```typescript
-function increment(a: number): number {
- return a + 1;
-}
+関数型の部分型の関係は複雑ですが、「部分型の値はより大きな型の値として扱える」という基本的な考え方に基づいています。次の3つの関数型は、すべて`(v: string) => string`型の部分型となります。
-// (a: number) => numberは(a: number, b: number) => numberの部分集合
-const operator2: BinaryNumberOperator = increment;
-```
+- `() => string`: 引数が少ない関数に、引数を余分に渡しても無視されるので安全
+- `(v: unknown) => string`: 関数の引数に、より小さな型の値を渡しても安全
+- `(v: string) => never`: 関数の戻り値を、より大きな型の値として扱っても安全
+
+:::
-### 型演算
+## 型演算
2 つの型に対し、集合の和や積 (共通部分)を求める記号が利用できます。
@@ -226,6 +350,10 @@ const operator2: BinaryNumberOperator = increment;
| `&` | 共通部分 |
| `\|` | 合併 |
+共通部分型は、**両方の型に属する型**を表します。**オブジェクト型同士の場合、両方の型のプロパティを全て持つオブジェクト型**となります。
+
+次の例における`Student & Programmer`型は、`name`、`major`、`language`の3つのプロパティを持つオブジェクト型となります。
+
```typescript
type Student = { name: string; major: string };
type Programmer = { name: string; language: string };
@@ -234,11 +362,57 @@ const studentProgrammer: Student & Programmer = {
major: "数学",
language: "TypeScript",
};
+```
+
+合併型は、**いずれかの型に属する型**を表します。
+次の例では、変数`hand`には`"グー"`、`"チョキ"`、`"パー"`のいずれかの文字列を指定できます。
+
+```typescript
const hand: "グー" | "チョキ" | "パー" = "グー";
```
-### 型推論
+変数の値が合併型のどれに属するかが、`if`文などの条件により明らかである場合、**型の絞り込み**が行われます。
+
+次の例における`meeting`引数は、`InPersonMeeting`型か`OnlineMeeting`型のいずれかです。`type`プロパティの値に応じて、`location`プロパティか`url`プロパティが利用可能になります。
+
+```typescript
+type InPersonMeeting = { type: "in_person"; location: string };
+type OnlineMeeting = { type: "online"; url: string };
+type Meeting = InPersonMeeting | OnlineMeeting;
+
+function describeMeeting(meeting: Meeting): string {
+ if (meeting.type === "in_person") {
+ return `会議は ${meeting.location} で行われます。`;
+ } else {
+ return `会議はオンラインで行われます。URL: ${meeting.url}`;
+ }
+}
+```
+
+### 確認問題
+
+**問題1**: `string & number`型は何型と等しいでしょうか。
+
+**問題2**: 次のように定義される型`T`に対して、直接使用可能なプロパティは何でしょうか。
+
+```typescript
+type T = { name: string; age: number } | { name: string; subject: string };
+```
+
+
+
+**問題1**: `never`型
+
+`string`型と`number`型の共通部分は存在しません。したがって、`string & number`型は、空集合である`never`型と等しいです。
+
+**問題2**: `name`のみ
+
+型`T`は、`{ name: string; age: number }`型と`{ name: string; subject: string }`型の合併型です。両方の型に共通して存在するプロパティは`name`のみであるため、型`T`に対して使用可能なプロパティは`name`のみとなります。
+
+
+
+## 型推論
文脈からデータ型が明らかな場合は、型定義の記述を省略できます。
@@ -269,7 +443,7 @@ window.onload = (e) => {
};
```
-### ジェネリクス
+## ジェネリクス
引数を一つ受け取り、その値をそのまま返す関数を考えてみよう。
@@ -279,7 +453,7 @@ function identity(x) {
}
```
-こういった関数では、引数`x`はどんな型の値も指定できます。つまり、`x`は`unknown`型とするのが適切なはずです。しかしながら、引数を`unknown`型としてしまうと、戻り値が`unknown`型となってしまい、戻り値に対する操作が一切不可能になってしまいます。
+こういった関数では、引数`x`はどんな型の値も指定できます。つまり、`x`は`unknown`型とするのが適切なはずです。しかし、引数を`unknown`型としてしまうと、戻り値が`unknown`型となってしまい、戻り値に対する操作が一切不可能になってしまいます。
```typescript
function identity(x: unknown) {
@@ -290,7 +464,7 @@ function identity(x: unknown) {
identity(1).toString();
```
-TypeScriptでは、型パラメータを用いることで、この問題を解決できます。型パラメータは、通常の引数と異なり、型を指定するための特殊な引数です。JavaScriptにトランスパイルされるタイミングで削除されます。
+TypeScriptでは、型パラメータを用いることで、この問題を解決できます。型パラメータは、通常の引数と異なり、型を指定するための特殊な引数です。JavaScriptにトランスパイルされる際に削除されます。こういった言語機能は他の多くのプログラミング言語でも用意されており、[ジェネリクス](https://www.typescriptlang.org/docs/handbook/2/generics.html)と呼ばれます。
```typescript
// Tは型パラメータ
@@ -307,17 +481,51 @@ identity(1).toString();
identity(1).toString();
```
-こういった言語機能は他の多くのプログラミング言語でも用意されており、[ジェネリクス](https://www.typescriptlang.org/docs/handbook/2/generics.html)と呼ばれます。
-
-`type`宣言でも型パラメータを利用できます。
+`type`宣言やクラスでも型パラメータを利用できます。
```typescript
+type Range = {
+ from: T;
+ to: T;
+};
+
+const dateRange: Range = {
+ from: new Date("2022-01-01"),
+ to: new Date("2022-12-31"),
+};
+
type BinaryOperator = (a: T, b: T) => T;
// addは(a: number, b: number) => number型
const add: BinaryOperator = (a, b) => a + b;
```
+### 確認問題
+
+次の関数`apply`は、関数を適用する関数です。引数と戻り値を表す型パラメータを定義し、ジェネリクスを用いて適切な型をつけてください。なお、`parseInt`関数は、文字列を整数に変換する関数です。
+
+```typescript
+function apply(f, x) {
+ return f(x);
+}
+
+const result = apply(parseInt, "1024"); // resultはnumber型
+```
+
+
+
+`parseInt`は、`(v: string) => number`型の関数です。したがって、`apply`関数の型パラメータ`T`は`string`、`U`は`number`に推論されます。
+
+```typescript
+function apply(f: (x: T) => U, x: T): U {
+ return f(x);
+}
+```
+
+
+
+
+
## TypeScriptとnpm
npmでインストールしたパッケージがTypeScriptに対応している場合、下の図のように、npmのパッケージのウェブサイトに アイコンが表示されます。
@@ -343,75 +551,3 @@ Viteは、標準でTypeScriptのトランスパイラが内蔵されています
[公式ドキュメント](https://www.typescriptlang.org/tsconfig)には、全てのオプションの詳細な説明が記述されています。特に、[`strict`オプション](https://www.typescriptlang.org/tsconfig#strict)は、TypeScriptの能力を大幅に上昇させることができるので、有効にすることが推奨されています。`typescript`パッケージを直接インストールしたプロジェクトでは、`npx tsc --init`コマンドによりこのファイルを生成できます。
:::
-
-## 演習問題1
-
-1. `string & number`型は何型と等しいでしょうか。
-2. 次のように定義される型`T`に対して使用可能なプロパティは何でしょうか。
-
- ```typescript
- type T = { name: string; age: number } | { name: string; subject: string };
- ```
-
-3. 次の型のうち、`(v: string) => string`型とみなせる (部分集合である) ものを全て選んでください。
- - `(v: unknown) => string`
- - `(v: never) => string`
- - `(v: string) => unknown`
- - `(v: string) => never`
-4. 次の関数`apply`は、関数を適用する関数です。ジェネリクスを用いて適切な型をつけてください (ヒント: 引数と戻り値を表す型パラメータを定義しましょう)。
-
- ```typescript
- function apply(f, x) {
- return f(x);
- }
- ```
-
-
-
-1. `never`型
-
- ```typescript
- type StringAndNumber = string & number; // never
- ```
-
-2. `name`のみ
-
- 2つの型に共通しているのは`name`プロパティだけなので、`T`型の変数に必ず存在しているプロパティは`name`のみとなります。よって、`name`のみ使用可能となります。
-
-3. `(v: unknown) => string`と`(v: string) => never`
-
- まず`(v: string) => never`に関してですが、こちらはなんとなく想像がつくかもしれません。`never`型はすべての型に含まれるため`string`型にも含まれますから、`(v: string) => string`とみなすことができるでしょう。
-
- 一方で、`(v: unknown) => string`型が答えになっているのは意外かもしれません。`unknown`型は`string`型を含むから間違いなのではないかと考えた方も多いでしょう。しかし、この理論で行くと少々不都合が生じます。例えば、次のようなコードを考えましょう。
-
- ```javascript
- type F = (arg: { name: string, math: number }) => number;
-
- function func(arg: { name: string, math: number, science: number }): number {
- console.log(arg.science);
- return arg.math;
- }
-
- const f: F = func;
- f({ name: "Tanaka", math: 100 });
- ```
-
- このコードでは、`{ name: string, math: number }`型は`{ name: string, math: number, science: number }`型を含んでいます。先ほどの`unknown`型と`string`型の関係と同じです。
-
- もしこのコードが通る場合、実際に渡された`{name: "Tanaka", math: 100}`には存在しないはずの`science`プロパティにアクセスできてしまうことになります。このようなことを防ぐために、引数の型が小さい集合になればなるほど、関数の型は大きな集合になる必要があります。
-
-4. 以下のコード
-
- ```typescript
- function apply(f: (x: T) => U, x: T): U {
- return f(x);
- }
- ```
-
-
-
-
-
-## 演習問題2(発展)
-
-フロントエンド・バックエンドともにTypeScriptを利用するアプリケーションを作成し、公開してみてください。