Python経験者のためのTypeScript入門（途中）

### はじめに

- コードの動作確認
  - TypeScript
    ちょっとしたコードの動作確認には [TypeScript Playground](https://www.typescriptlang.org/play) が便利。
    コードを書いて`Run`ボタンで実行できる。TypeScriptの環境を作るのは結構めんどくさいので重宝する。

  - Python
    [Trinket CodePlayground](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwis_p26ntz3AhWGG6YKHfZVB7UQFnoECAYQAQ&url=https%3A%2F%2Ftrinket.io%2Fpython%2Ff7ad7f9864&usg=AOvVaw3yRsrTbSXljPcjQRJYAjIh) などがあるが、適当な`.py` ファイル作って実行すればいいと思う。

---

### プリミティブ型の変数定義

  - Python
    Pythonには var, let, const のような使い分けはなく、全てTypeScriptのletと同じ。

  - TypeScript
    TypeScriptの変数定義には var, let, const の３種類ある。
    可能な限り const を使う。

    var と let はどちらも再代入可能な変数を定義するが、
    var は古い書き方なので使わない方が良い。全て let でOK。

    > [JavaScriptでvarが非推奨な理由を整理してみた](https://qiita.com/taiju_suzuki/items/49ed558bd837452353b8)

    const は定数を定義する。
    const でもオブジェクト型であれば要素の変更はできる。


##### Python

  ```python
  s: str = 'string'
  i: int = 0
  f: float = 0.0
  b: bool = True

  # 再代入
  s = s + 'xxx'  # or s += 'xxx'
  i = i + 100    # or i += 100
  f = f + 100.0  # or f += 100.0
  b = not b

  print(s)  # 'stringxxx'
  print(i)  # 100
  print(f)  # 100.0
  print(b)  # False
  ```

##### TypeScript

`let`

```TypeScript
let s: string = 'string'
let i: number = 0    // TypeScriptの型に integer はない。
let f: number = 0.0  // TypeScriptの型に  float  はない。
let b: boolean = true

// 再代入
s = s + 'xxx'  // or s += 'xxx'
i = i + 100    // or i += 100
f = f + 100.0  // or f += 100.0
b = !b

console.log(s)  // "stringxxx"
console.log(i)  // 100
console.log(f)  // 100
console.log(b)  // false
```

`const`


```TypeScript
const s: string = 'string'
const i: number = 0
const f: number = 0.0
const b: boolean = true

// 再代入は全て不可（エラー）
s = s + 'xxx'  // or s += 'xxx'
i = i + 100    // or i += 100
f = f + 100.0  // or f += 100.0
b = !b
```

---

### オブジェクト型の変数定義（配列）

##### Python

```python
a: [str, int, float, bool] = ['string', 0, 0.0, True]

# 要素の変更
a[0] = a[0] + 'xxx'  # or a[0] += 'xxx'
a[1] = a[1] + 100    # or a[1] += 100
a[2] = a[2] + 100.0  # or a[2] += 100.0
a[3] = not a[3]

print(a[0])  # 'stringxxx'
print(a[1])  # 100
print(a[2])  # 100.0
print(a[3])  # False

# 再代入
a = ['str', 9, 9.9, False]
print(a)  # ['str', 9, 9.9, False]
```

##### TypeScript

`let`

```TypeScript
let a: [string, number, number, boolean] = ['string', 0, 0.0, true]

// 要素の変更
a[0] = a[0] + 'xxx'  // or a[0] += 'xxx'
a[1] = a[1] + 100    // or a[1] += 100
a[2] = a[2] + 100.0  // or a[2] += 100.0
a[3] = !a[3]

console.log(a[0])  // 'stringxxx'
console.log(a[1])  // 100
console.log(a[2])  // 100.0
console.log(a[3])  // false

// 再代入
a = ['str', 9, 9.9, false]
console.log(a)  // ['str', 9, 9.9, false]
```

`const`

```TypeScript
const a: [string, number, number, boolean] = ['string', 0, 0.0, true]

// 要素の変更は可能（ a への再代入ではないため）
a[0] = a[0] + 'xxx'  // or a[0] += 'xxx'
a[1] = a[1] + 100    // or a[1] += 100
a[2] = a[2] + 100.0  // or a[2] += 100.0
a[3] = !a[3]

// 再代入は不可（エラー）
a = ['str', 9, 9.9, false]
```

---

### オブジェクト型の変数定義（辞書 or オブジェクト）

##### Python

```python
from typing import TypedDict


class Dic(TypedDict):
    s: str
    i: int
    f: float
    b: bool


dic: Dic = {
    's': 'string',
    'i': 0,
    'f': 0.0,
    'b': True,
}

# 要素の変更
dic['s'] = dic['s'] + 'xxx'  # or dic['s'] += 'xxx'
dic['i'] = dic['i'] + 100    # or dic['i'] += 100
dic['f'] = dic['f'] + 100.0  # or dic['f'] += 100.0
dic['b'] = not dic['b']

print(dic['s'])  # 'stringxxx'
print(dic['i'])  # 100
print(dic['f'])  # 100.0
print(dic['b'])  # False

# 再代入
dic = {
    's': 'str',
    'i': 9,
    'f': 9.9,
    'b': False,
}
print(dic)  # {'s': 'str', 'i': 9, 'f': 9.9, 'b': False}
```

##### TypeScript

`let`

```TypeScript
interface Obj {
    s: string
    i: number
    f: number
    b: boolean
}

let obj: Obj = {
    s: 'string',
    i: 0,
    f: 0.0,
    b: true,
}

// 要素の変更
obj.s = obj.s + 'xxx'  // or obj.s += 'xxx'
obj.i = obj.i + 100    // or obj.i += 100
obj.f = obj.f + 100.0  // or obj.f += 100.0
obj.b = !obj.b

console.log(obj.s)  // 'stringxxx'
console.log(obj.i)  // 100
console.log(obj.f)  // 100
console.log(obj.b)  // false

// 再代入
obj = {
    s: 'str',
    i: 9,
    f: 9.9,
    b: false,
}
console.log(obj)  // { 's': 'str', 'i': 9, 'f': 9.9, 'b': false }
```

`const`

```TypeScript
interface Obj {
    s: string
    i: number
    f: number
    b: boolean
}

const obj: Obj = {
    s: 'string',
    i: 0,
    f: 0.0,
    b: true,
}

// 要素の変更は可能（ obj への再代入ではないため）
obj.s = obj.s + 'xxx'  // or obj.s += 'xxx'
obj.i = obj.i + 100    // or obj.i += 100
obj.f = obj.f + 100.0  // or obj.f += 100.0
obj.b = !obj.b

// 再代入は不可（エラー）
obj = {
    s: 'str',
    i: 9,
    f: 9.9,
    b: false,
}
```

「const=定数 なのに変更できるってどういうこと？」ってなりそう。

Pythonだけやってた私は結構混乱しましたが、型エラーで悩んでるうちに理解できました。

> 参考: [ミュータブルな型とイミュータブルな型の相違を知ろう](https://qiita.com/makotoo2/items/fc3a617882916f9775f5)

とりあえずオブジェクト型は const で定義してみましょう。

オブジェクト型を再代入しなければならない状況はあまりありません。


##### アトリビュートの参照についての補足

##### Python

`dic['key']`で参照する

```python
my_dict = {'key': 'value'}

# この書き方はできない
my_dict.key  # 例外 AttributeError: 'dict' object has no attribute 'key'.

my_dict['key']        # 'value'
my_dict['wrong_key']  # 例外 KeyError: 'wrong_key'

# .get()を使うことで例外を避けることができる
my_dict.get('key')    # 'value'
my_dict.get('wrong_key')  # None

# .get()の第２引数を渡すことでキーがなかった場合のデフォルト値を設定できる
my_dict.get('wrong_key', 'default_value')  # default_value
```

##### TypeScript

`obj.key`, `obj['key']`どちらでも参照できる。
Pythonと異なり、存在しないキーを参照しようとしても例外にはならない。

```typescript
const myObj = { key: 'value' }

// どちらでもちゃんと 'value' を参照できる
myObj.key     // こっちの方が短く書けて良い
myObj['key']  // キー名に日本語が含まれる場合などはこちらを使う必要がある

myObj.wrongKey      // undefined
myObj['wrong_key']  // undefined

// ? を付けることで型エラーを回避できることも
myObj?.wrongKey      // undefined
```

---

### 特殊な型

Pythonの None とTypeScriptの null は同じと考えていい。

TypeScriptには [undefined](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/undefined) や [Symbol](https://developer.mozilla.org/ja/docs/Glossary/Symbol) があるが、[Symbol](https://developer.mozilla.org/ja/docs/Glossary/Symbol) を使用する機会はかなり少ない。

まだ値が代入されていない変数は undefined 型となる。

```typescript
let x
console.log(x) // undefined
```

Pythonで同じことをしようとすると例外になる。

```python
print(x)

Traceback (most recent call last):
File "C:\Program Files\Python310\lib\code.py", line 90, in runcode
  exec(code, self.locals)
File "<input>", line 1, in <module>
NameError: name 'x' is not defined
```

---

### 関数の定義

##### Python
```python
from typing import TypedDict


class Dic(TypedDict):
    k1: str
    k2: int
    k3: float
    k4: bool


def my_func(s: str, i: int, f: float, b: bool) -> Dic:
    return {'k1': s, 'k2': i, 'k3': f, 'k4': b}

# デフォルト値ありの関数
def my_func_with_default(s: str = '', i: int = 0, f: float = 0.0, b: bool = False) -> Dic:
    return {'k1': s, 'k2': i, 'k3': f, 'k4': b}


if __name__ == '__main__':
    x = my_func('string', 0, 0.0, True)
    print(x)  # {'k1': 'string', 'k2': 0, 'k3': 0.0, 'k4': True}

    # 全てデフォルト値
    y = my_func_with_default()
    print(y)  # {'k1': '', 'k2': 0, 'k3': 0.0, 'k4': False}

    # 引数を１つだけ指定
    z = my_func_with_default('custom string')
    print(z)  # {'k1': 'custom string', 'k2': 0, 'k3': 0.0, 'k4': False}
```

##### TypeScript

```typescript
interface Obj {
    k1: string
    k2: number
    k3: number
    k4: boolean
}

function myFunc(s: string, i: number, f: number, b: boolean): Obj {
    return { k1: s, k2: i, k3: f, k4: b }
}

// デフォルト値ありの関数
function myFuncWithDefault(s: string = '', i: number = 0, f: number = 0.0, b: boolean = false): Obj {
    return { k1: s, k2: i, k3: f, k4: b }
}

const x = myFunc('string', 0, 0.0, true)
console.log(x)  // {"k1": "string", "k2": 0, "k3": 0, "k4": true}

// 全てデフォルト値
const y = myFuncWithDefault()
console.log(y)  // {"k1": "", "k2": 0, "k3": 0, "k4": true}

// 引数を１つだけ指定
const z = myFuncWithDefault('custom string')
console.log(z)  // {"k1": "custom string", "k2": 0, "k3": 0, "k4": false}
```

##### TypeScriptの[アロー関数](https://typescript-jp.gitbook.io/deep-dive/future-javascript/arrow-functions)

TypeScriptの関数定義方法にはいろんな書き方があり、結構混乱します。
以下の３つの関数は全て同じ動作をします。

> 厳密には通常の関数とアロー関数ではthis(Pythonのself)の扱いが異なります

```typescript
// 通常の関数
function myFunc(x: string): string {
    return x
}

// アロー関数
const myArrowFunc = (x: string): string => {
    return x
}

// アロー関数定義 + return の省略
const myArrowFunc2 = (x: string): string => x

myFunc('aaa')
myArrowFunc('bbb')
myArrowFunc2('ccc')
```

---

### クラスの定義
##### Python

```python
```

##### TypeScript

```typescript
```

---

### 文字列の操作
##### Python

```python
```

##### TypeScript

```typescript
```

---

### 数値の操作
##### Python

```python
```

##### TypeScript

```typescript
```

---

### 配列の操作
##### Python

```python
```

##### TypeScript

```typescript
```

---

### オブジェクト(辞書)の操作
##### Python
```python
```

##### TypeScript

```typescript
```

---

### 条件分岐
##### Python
```python
if x > 5:
  print('x > 5')
elif x == 5:
    print('x == 5')
elif x != -100:
    print('x != -100')
else:
    print('else')

# ----------------------------
if 0 < x and x <= 10:
    print('0 < x and x <= 10')

# こう書いた方がいい
if 0 < x <= 10:
    print('0 < x <= 10')

# ----------------------------
if (0 < x and 0 <= y) or (100 < x and 100 <= y):
    print('(0 < x and 0 <= y) or (100 < x and 100 <= y)')

# ----------------------------
# None の判定は is を使う
if x is None:
    print('x is None')
    
if y is not None:
    print('y is not None')
```

##### TypeScript

`==`,  `!=` は基本使わないこと。
`===`,  `!==` を使う。

```typescript
// なんとこれはtrueになる
if (0 == '0') {
    console.log("0 == '0'")
}

// === で判定すればちゃんと false になる
if (0 === '0') {
    console.log("0 === '0'")
}

// ----------------------------
if (x > 5) {
    console.log('x > 5')
} else if (x === 5) {
    console.log('x === 5')
} else if (x !== -100) {
    console.log('x !== -100')
} else {
    console.log('else')
}

// ----------------------------
if (0 < x && x <= 10) {
    console.log('0 < x && x <= 10')
}

// ----------------------------
if ((0 < x && 0 <= y) || (100 < x && 100 <= y)) {
    console.log('(0 < x && 0 <= y) || (100 < x && 100 <= y)')
}

// ----------------------------
// １行であれば {} を省略できる。（Pythonでもできるが非推奨）
if (true) console.log('true')
else console.log('false')
```

---

### 繰り返し

##### Python

`for ... in ... :`の形。
配列でインデックスも取得したいなら `enumerate()` を使う。
辞書なら `.keys()`,  `.values()`, `.items()`を使う。

```python
for x in range(10):  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    if x == 3:
        continue  # for文の先頭に戻る
    elif x == 8:
        break  # for文を抜ける
    else:
        print(x)  # 0 -> 1 -> 2 -> 4 -> 5 -> 6 -> 7

# 配列 -------------
items = ['a', 'b', 'c']

for item in items:
    print(item)  # 'a' -> 'b' -> 'c'

for i, item in enumerate(items):
    print(i)     # 0 -> 1 -> 2
    print(item)  # 'a' -> 'b' -> 'c'

# 辞書 -------------
dic = {
  'k1': 'v1',
  'k2': 'v2',
  'k3': 'v3'
}

for k in dic.keys():
    print(k)       # 'k1' -> 'k2' -> 'k3'
    print(dic[k])  # 'v1' -> 'v2' -> 'v3'

for v in dic.values():
    print(v)  # 'v1' -> 'v2' -> 'v3'

for k, v in dic.items():
    print(k)       # 'k1' -> 'k2' -> 'k3'
    print(dic[k])  # 'v1' -> 'v2' -> 'v3'
```

##### TypeScript

`for (... in ...)`と`for (... of ...)` があって少しややこしいが、基本`of`だけでいい。
`in`は配列であればインデックス(何故かnumberではなくstring)、オブジェクトであればキーを取得する。
`of`は配列であれば要素、オブジェクトであれば値を取得する。

> [配列にfor...inはダメと言うけれど、](https://qiita.com/may88seiji/items/2e8fec8391fea0a17dbc)

配列でインデックスも取得したいなら `.entries()` を使う。インデックスのみ取得したいなら `.keys()`を使う。
辞書なら `Object.keys(...)`,  `Object.values(...)`, `Object.entries(...)`を使う。

```typescript
for (const x of Array.from(Array(10), (_, i) => i)) {  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  if (x === 3) continue    // for文の先頭に戻る
  else if (x === 8) break  // for文を抜ける
  else console.log(x)  // 0 -> 1 -> 2 -> 4 -> 5 -> 6 -> 7
}

// 配列 -------------
const items = ["a", "b", "c"]

for (const i in items)
    // i は string。何故…
    console.log(i)  // "0" -> "1" -> "2"

for (const i of items.keys())
    console.log(i)  // 0 -> 1 -> 2

for (const item of items)
    console.log(item)  // "a" -> "b" -> "c"

for (const [i, item] of items.entries()) {
    console.log(i)     // 0 -> 1 -> 2
    console.log(item)  // "a" -> "b" -> "c"
}

// オブジェクト -------------
const obj: Record<string, string> = {
  "k1": "v1",
  "k2": "v2",
  "k3": "v3"
}

for for (const k of Object.keys(obj)) {  // または 
    console.log(k)       // "k1" -> "k2" -> "k3"
    console.log(obj[k])  // "v1" -> "v2" -> "v3"
}

for (const v of Object.values(obj))
    console.log(v)  // "v1" -> "v2" -> "v3"

for (const [k, v] of Object.entries(obj)) {
    console.log(k)  // "k1" -> "k2" -> "k3"
    console.log(v)  // "v1" -> "v2" -> "v3"
}
```

---

### 型の判定
##### Python
```python
```

##### TypeScript

```typescript
```

---

### 型の変換(キャスト)
##### Python
```python
```

##### TypeScript

```typescript
```

### 例外処理
##### Python
```python
```

##### TypeScript

```typescript
```

---

### 言語固有の技

##### Python

- リスト内包表記

  ```python
  array = [1, 2, 3, 4, 5]
  new_array = [x for x in array if x > 2]

  print(new_array)  # [3, 4, 5]

  # 辞書でも同じようなことができる
  dic = {'k1': 1, 'k2': 2, 'k3': 3, 'k4': 4, 'k5': 5}
  new_dic = {k: v for k, v in dic.items() if v > 2}

  print(new_dic)  # {'k3': 3, 'k4': 4, 'k5': 5}

  # 1～100の範囲で奇数のみの配列を作る、なんてことも１行で可能
  print([x for x in range(100) if x % 2])  # [1, 3, 5, 7, 9 ... , 99]
  ```

##### TypeScript

- [分割代入](https://typescript-jp.gitbook.io/deep-dive/future-javascript/destructuring)

  ```typescript
  const obj = { k1: 1, k2: 2, k3: 3, k4: 4, k5: 5 }
  const { k1, k2, k3, k4, k5 } = obj

  console.log(k1)  // 1
  console.log(k2)  // 2
  console.log(k3)  // 3
  console.log(k4)  // 4
  console.log(k5)  // 5
  ```

- [スプレッド演算子](https://typescript-jp.gitbook.io/deep-dive/future-javascript/spread-operator)

- [分割代入](https://typescript-jp.gitbook.io/deep-dive/future-javascript/destructuring) + [スプレッド演算子](https://typescript-jp.gitbook.io/deep-dive/future-javascript/spread-operator)

  ```typescript
  const obj = { k1: 1, k2: 2, k3: 3, k4: 4, k5: 5 }
  const { k1, k2, ...other } = obj
  console.log(k1)     // 1
  console.log(k2)     // 2
  console.log(other)  // { "k3": 3, "k4": 4, "k5": 5 } 
  ```