PythonユーザーのためのTypeScript入門

# はじめに
この記事はそれなりにPythonを使ったことのある人間がとりあえずTypeScriptを書き始めることが出来るレベルになることを目標としています。
Python側の事情もそこそこに書いたのでもしかしたらTypeScriptユーザーのためのPython入門にもなる、かもしれません。

## Why Python and TypeScript?
ヨーロッパ圏の言語を勉強する際に似た系統の言語を一緒に覚えると学習効率がいいという話を聞いたことがありませんか？
プログラミング言語も同じで、新しい言語を学習する際は似た言語に絡めて覚えると習得が早いような気がしています。

近年PythonはTypeScriptを参考に型システムを拡張しており近しい仕組みが数多く存在します。またそれ以外にも同じスクリプト言語であり、クラスベースであり、関数が第一級オブジェクトであり、総合的に見て似たような言語だと言えそうです。ユースケースも一部被ってますしね。

なので今回はこの2つの言語を例に説を立証してみようと思いました。
被検体は私(Python歴3年、TS歴3ヶ月)、そしてあなたです。よろしくお願いします。

# 開発環境
## 実行環境
[**Python**](https://www.python.org/)みたいにとりあえずサクッとコンソールで動かせる環境が欲しい場合はnode + ts-node(後述)がいいかと思います。
ただじゃあ早速[**node.js**](https://nodejs.org/ja/)をインストールして…というのはちょっと待ってください。
あなたはそれをやってPythonで痛い目を見たことがありませんか？

### 実行環境のバージョン管理
これです。どうせ実行環境は複数用意することになるので最初から入れておきましょう。
現状のデファクトスタンダードは[**nvm**](https://github.com/nvm-sh/nvm)ですが最近は[**Volta**](https://github.com/volta-cli/volta)も人気っぽいですね。

### パッケージ管理
[**npm**](https://github.com/npm/cli)と[**yarn**](https://github.com/yarnpkg/yarn)という二大派閥があります。[**Pipenv**](https://github.com/pypa/pipenv)と[**Poetry**](https://github.com/python-poetry/poetry)みたいなもんです。

### とりあえず作るぞ
というわけでとりあえず最もシンプルな構成(nvm + node + npm + ts-node)で実行環境を作っていきます。
まずnvm。UNIX系の方は以下の指示に従ってスクリプトを実行してください。

https://github.com/nvm-sh/nvm/tree/v0.37.2#installing-and-updating

Windowsはここから最新のnvm-setup.zipをダウンロードしてインストールしましょう。

https://github.com/coreybutler/nvm-windows/releases

以下は共通です。node + npm(+ npx)のインストール。

```shell
$ nvm install lts  # 安定バージョンのインストール。最新バージョンを入れたい場合は`latest`
$ nvm use xx.xx.x  # ↑のコマンドを実行したときに指示がなければ必要なし
```

プロジェクトのフォルダを作ったら移動してtypescriptとts-nodeをインストール。

```shell
$ npm init -y
$ npm install typescript ts-node -D  # dev-dependencies
```

### Hello, world!
```shell
$ npx ts-node

> console.log("Hello, world!")
Hello, world!
```

こんにちは、世界！

### 余談: ts-nodeってなに？
お気付きかと思いますが実はnvmもnodeもnpmもTypeScriptではなく**JavaScript**のお話でした。
試しに適当な場所でJSのハロワをしてみましょう。

```shell
$ node

> console.log("Hello, world!")
Hello, world!
```

出来ましたね。
本来TSを実行する際は.tsファイルから.jsファイルを生成してnodeに与える必要があるのですが、
それをショートカットするため**プロジェクトに**インストールするものがts-nodeです[^local]。
よってts-nodeを入れたプロジェクト以外では直でTSを使うことは出来ないのでご注意ください。


## エディター(IDE) / リンター / フォーマッター
この3つはもう切っても切れない関係なのでセットで考えることをお勧めします。
つまりこのリンター/フォーマッターは自分が使っているエディターで動くのか？ということを考えましょうということですね。
PythonマンはVS Codeを使っていることが多いはずなのでここではVS Codeでリンター/フォーマッターを動かすことを考えます。

### リンター
リンターは[**ESLint**](https://github.com/eslint/eslint)の一強です。

```shell
$ npm install eslint -D
```

入れたらまず設定ファイルを書かねばならないのですが、ひとまず置いておいて次へ進みましょう。

### フォーマッター
フォーマッターについては議論があります。ESLintのフォーマット機能を使うか、[**prettier**](https://github.com/prettier/prettier)を使うかです。
prettierは細かいカスタマイズを極力許さない思想が特徴のフォーマッターであり、分かりやすく言えばPythonにおける[**black**](https://github.com/psf/black)的なポジションです(blackほど厳しくはないですが)。
私はblackの信徒なのでprettierを入れます。

```shell
$ npm install prettier -D
```

blackとflake8がぶつかるように、prettierもESLintとぶつかることがあるのでこれを解消するために[**eslint-config-prettier**](https://github.com/prettier/eslint-config-prettier)も入れます。

```shell
$ npm install eslint-config-prettier -D
```

### 設定ファイル
設定ファイルの作成を後回しにしたのはフォーマッターに何を使うかによって設定が変わるためです。
具体的に言うと

```shell
$ npx eslint --init
```

コマンドでESLintと対話を始めた際の最初の質問、

```shell
How would you like to use ESLint?
```

に対する答えがESLintを使う場合は

```shell
> To check syntax, find problems, and enforce code style
```

に、prettierを使う場合は

```shell
> To check syntax and find problems
```

となります。
その他の問いに対しては今回はこんな感じになるかと↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245527/a0357ab0-b4ef-5d07-1b55-d7b28d55042d.png)
これでプロジェクトのルートに`.eslintrc.yml`が生成されますが、prettierを使う場合はさらにもうひと手間。

```yaml:./eslintrc.yml
extends:  # この項目の最後に
  - eslint:recommended
  ...
  - prettier  # これを追加します
```

また同じくルートに`.prettierrc.yml`を作ることでprettierrcの設定が可能ですが、前述の通りprettierは細かいカスタマイズを良しとしないため極力項目は少なくしましょう。

```yaml:./.prettierrc.yml
printWidth: 100
semi: false  # 行末のセミコロンだけは許せないので
```

参考:
[**TypeScript プロジェクトに ESLint を導入する - まくろぐ**](https://maku.blog/p/xz9iry9/)
[**TypeScript コードを Prettier で自動整形する - まくろぐ**](https://maku.blog/p/au8iu6u/)

そして最後にもう1つ重要な設定ファイルを作ります。

```shell
$ npx tsc --init  # 成功するとルートにtsconfig.jsonが生成されます
```

これが何かとか、どんな設定があるのかとかは今は考えなくて大丈夫です。調べるとげんなりしちゃうので。
ただしこのコマンドだけは今やっておかないと**null安全でない**TypeScriptを書くことになるので必ず実行しておいてください。

###VS Codeの設定
[**ESLint**](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)、[**prettier**](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)それぞれの拡張機能をインストールして設定を書きます。

```json:./vscode/settings.json
{
    "typescript.format.enable": false,
    "[typescript]": {
        "editor.formatOnSave": true,
        "editor.formatOnType": true,
        "editor.formatOnPaste": true,
    },
}
```

`"typescript.format.enable": false`はVS Code標準のTSフォーマッターを切る設定なので必須、下3行はお好みです。

適当なファイルを作って正しく動作しているか確認してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245527/6494c35a-cc19-4947-f160-5a855041fcfe.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245527/03b330c1-0a6e-9602-c5a1-81cb4b81e422.png)
OK.

prettierが正しく動いているかどうかは設定ファイルを弄ってみると分かりやすいです。

```./.prettierrc.yml
semi: true  # 変更
```

この状態で`sample.ts`を保存すると…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245527/94784116-2a48-65bb-526e-1e79335e617f.png)
大丈夫ですね。

参考:
[**VS Code のフォーマッターで自動整形する (editor.formatOnSave) - まくろぐ**](https://maku.blog/p/qcoz9ju/)

## デバッグ
Pythonを書いていたときは何も考えずにF5を押せばデバッグが出来ていたと思いますが、TSの場合は少しだけ設定が必要です。

```json-doc:多くの人がデフォルトのまま使っているであろうPythonプロジェクトのlaunch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}
```

```json-doc:node+ts-nodeのlaunch.json(./.vscode/launch.json)
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "TypeScript: Current File",
            "type": "pwa-node",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
            "skipFiles": ["<node_internals>/**", "${workspaceRoot}/node_modules/**"],
        }
    ]
}
```

Pythonの時に比べて増えたのは下2行ですね。これでts-nodeを経由してnodeでデバッグすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245527/b549792a-f4ef-49bb-191b-f5fb5001da5b.png)
ブレークポイントも設定可能です。


## テンプレート

https://github.com/ikumi-rai/TypeScript-VSCode-Template

ここまでの内容をまとめたテンプレートリポジトリを用意しました。
各ファイルの置き場所確認などにご活用ください。

# 基本文法
ここからはいよいよ仕様や文法の比較に入ります。まずは構造化プログラミングにおける基本の基から。
なお以降JSの機能もまとめてTSと表記します(呼びわけが面倒なので)がその点ご容赦ください。

## if
Pythonでは`if`文を1行で書くとblackに殴られますがprettierには怒られません。

```pyx:py
import random

if random.random() > 0.5:
    print("big")

# ng(blackが許さないだけで文法的に間違いではありません)
if random.random() > 0.5: print("big")
```

```typescript:ts
if (Math.random() > 0.5) {
  console.log("big")
}

// ok
if (Math.random() > 0.5) console.log("big")
```

### 論理演算子
短絡評価であり最後に評価した**値**を返す仕様はTSも同様です。

```pyx:py
print(0 and 1)  # falseyな「値」が返る
> 0
print(0 and "")  # 両辺がfalseyの場合は左辺が返る
> 0
print(1 and "a")  # 両辺がtruthyの場合は右辺が返る
> a
```

```typescript:ts
console.log(0 && 1)
> 0
console.log(0 && "")
> 0
console.log(1 && "a")
> a
```

```pyx:py
print(0 or 1)  # truthyな「値」が返る
> 1
print(0 or "")  # 両辺がfalseyの場合は右辺が返る
> 
print(1 or "a")  # 両辺がtruthyの場合は左辺が返る
> 1
```

```typescript:ts
console.log(0 || 1)
> 1
console.log(0 || "")
> 
console.log(1 || "a")
> 1
```

```pyx:py
print(not (1 or "a"))  # 否定をつけるとbool値になる
> False
```

```typescript:ts
console.log(!(1 || "a"))
> false
```

#### truthy / falsey
Pythonでは自作オブジェクトの真偽を自分で決められましたが、TSにおいてオブジェクトは常に真です。
つまり何がtruthyで何がfalseyかは全て事前に決まっているということですね。
[**TypeScript Deep Dive 日本語版**](https://typescript-jp.gitbook.io/deep-dive/recap/truthy)様より便利な表を引用します。

| 変数の型 | falsyな値 | truthyな値 |
| --- | --- | --- |
| `boolean` | `false` | `true` |
| `string` | `''` (空文字列) | その他の文字列 |
| `number` | `0` `NaN` | その他の数値 |
| `null` | 常にfalsy | なし |
| `undefined` | 常にfalsy | なし |
| その他のオブジェクト<br>(`{}`や`[]`といった空のものも含む) | なし | 常にtruthy |

せっかくなので残りのプリミティブ型も表に加えましょう。
(説明が前後しますがTSにおける値はプリミティブ型とオブジェクト型とに大別され、プリミティブ型はここに挙げた8種で全てです。)

| 変数の型 | falsyな値 | truthyな値 |
| --- | --- | --- |
| `function` | なし | 常にtruthy |
| `bigint` | `0n` | その他の数値 |
| `symbol` | なし | 常にtruthy |

だいたいPythonと同じですね。`[]`や`{}`がtruthyなことだけ注意が必要そうです。

### 比較演算子
TSの比較演算子は事実上1つしかないので若干不便です。

```pyx:py
hoge = []
fuga = []

print(hoge == fuga)  # ==はオブジェクトが等価であるかを見るためtrue
> true
print(hoge is fuga)  # isはオブジェクトが同一であるか(参照値が同じか)を見るためfalse
> false
```

```typescript:ts
const hoge = []
const fuga = []

console.log(hoge === fuga)  // ===でオブジェクト同士を比較する場合は同一であるかを見る(Pythonのisと同じ)
> False
console.log(1 === 1)  // プリミティブ同士を比較する場合は等価であるかを見る(Pythonの==と同じ)
> True
```

じゃあオブジェクト同士が同じ値を持つかどうかはどうやって？という感じなんですが、
どうやら[**deep-equal**](https://www.npmjs.com/package/deep-equal)というサードパーティ製ライブラリを使うらしい…。
本当ですか？どなたか他の方法をご存じでしたら教えてください。


### 三項演算子
PythonとTSでは式の順番が異なりますがやることは同じです。

```pyx:py
import random
"big" if random.random() > 0.5 else "small"
```

```typescript:ts
Math.random() > 0.5 ? "big" : "small"
```

## for
Pythonの`for`はTSで言うと`for...of`になります。

```pyx:py
list_ = ["hoge", "fuga", "piyo"]

for i in range(len(list_)):
    print(i)
> 0
> 1
> 2

for elm in list_:
    print(elm)
> hoge
> fuga
> piyo

for i, elm in enumerate(list_):
    print(i, elm)
> 0 hoge
> 1 fuga
> 2 piyo
```

```typescript:ts
const list_ = ["hoge", "fuga", "piyo"]

for (let i = 0; i < list_.length; i++) {
  console.log(i)
}
> 0
> 1
> 2

for (const elm of list_) {
  console.log(elm)
}
> hoge
> fuga
> piyo

list_.forEach((elm, i) => {
  console.log(i, elm)
})
> 0 hoge
> 1 fuga
> 2 piyo


// {}は省略してもok
for (let i = 0; i < list_.length; i++) console.log(i)
for (const elm of list_) console.log(elm)
list_.forEach((elm, i) => console.log(i, elm))
```

TSのループ文はたくさんあるので詳しくは[**こちら**](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Loops_and_iteration)をご確認ください。
ちなみにPythonで言う`for...else`に該当する構文はありません[^else]。

## 変数
### 定義

ここはさすがにベースが弱い動的型付けであるPythonと言語仕様として強い静的型付けであるTSとの差を感じます。

```pyx:py
hoge = "hoge"  # 変数は突然代入してよい
hoge = 1  # ok

fuga: str = "fuga"  # fugaをstr型として定義したので
fuga = 2  # これはng

from typing import Final
piyo: Final = "piyo"  # 再代入を禁止する書き方も一応あります
piyo = "piyopiyo"  # ng
```

```typescript:ts
let hoge = "hoge"  // この定義でhogeはstring型になる(型推論)ので
hoge = 1  // 異なる型は代入できなくなる

let fuga: string = "fuga"  // 上記の理由から`: string`を書く必要がないのでこの書き方はng

const piyo = "piyo"  // 再代入禁止
piyo = "piyopiyo"  // ng
```

基本的には全部`const`で定義してどうしても再代入したい場合だけ`let`を使うといいと思います。
あと`var`というのもあるのですが後方互換のために残してあるだけなので忘れてください。

### スコープ

Pythonのスコープを切るものはモジュール/クラス/関数の3つ、TSのスコープを切るものはブロック[^scope]というとなんか違うように感じますが実際にはPythonよりちょっと狭いくらいで基本的な考え方は一緒です。

```pyx:py
list_ = ["hoge", "fuga", "piyo"]

for elm in list_:
    elm

print(elm)  # elmは生きてるのでエラーにならない(今確認してみたらPylanceには怒られるようになってました。いつから？)
> piyo
```

```typescript:ts
const list_ = ["hoge", "fuga", "piyo"]

for (const elm of list_) {
  elm
}

console.log(elm)  // elmはブロック内で死んでるのでエラー
```

双方ともスコープ外の変数は巻き上げられる。

```pyx:py
def print_hoge() -> None:
    print(hoge)

hoge = "hoge"
print_hoge()
> hoge
```

```typescript:ts
function logHoge(): void {
  console.log(hoge)
}

const hoge = "hoge"
logHoge()
> hoge
```

トップレベルで定義したものの扱いだけが若干面倒で、

- そのファイル(.ts)内に`import`もしくは`export`が1つでも書かれている場合はモジュールレベル(他のファイルから見えない)
- 1つもない場合はグローバル(他のファイルから見える)

になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245527/c536dbda-f572-f395-9f77-ec6df4ddf1e1.png)
↑ `hoge`と`logHoge`がよそから見えている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245527/75e5068d-df94-7e76-b5cc-f1143e010739.png)
↑ `hoge`だけが見えていて`logHoge`が見えなくなった

Pythonでバシバシトップレベルで定義することに慣れている方はご注意ください。

参考:
[**ES2015のモジュール管理 | Think IT**](https://thinkit.co.jp/article/10644)

## 関数
### 定義
```pyx:py
def log(msg: str) -> None:
    print(msg)
```

```typescript:ts
function log(msg: string): void {
  console.log(msg)
}
```

基本構文は同じですね。TSの方は戻り値が`void`と書いてありますが実際には`undefined`が返ります。

### 色々な引数

```pyx:py
# オプション引数
def log1(msg: str | None = None) -> None:
    print(msg)

# デフォルト引数
def log2(msg: str = "hoge") -> None:
    print(msg)

# キーワード引数
def log3(*, msg: str) -> None:
    print(msg)

# 可変長引数
def log4(*msgs: str) -> None:
    print(msgs)

# 可変長キーワード引数
def log5(**msgs: str) -> None:
    print(msgs)
```

```typescript:ts
// オプション引数
function log1(msg?: string): void {
  console.log(msg)
}

// デフォルト引数
function log2(msg = "hoge"): void {
  console.log(msg)
}

// キーワード引数
function log3(option: { msg: string }): void {
  console.log(option.msg)
}

// 可変長引数
function log4(...msgs: string[]): void {
  console.log(msgs)
}

// 可変長キーワード引数
function log5(option: object): void {
  console.log(option)
}
```

### 引数の渡され方
関数の仮引数には何が渡されるかと言うと、これもPythonと同じく参照値が渡されます。
参照の値渡しとか共有渡しとか呼ばれるアレです。
よってオブジェクトを渡した場合には副作用が生じる可能性があります。

```pyx:py
def log(msgs: list[str]) -> None:
    msgs.append("piyo")
    print(msgs)

msgs = ["hoge", "fuga"]
log(msgs)
> ["hoge", "fuga", "piyo"]
log(msgs)
> ["hoge", "fuga", "piyo", "piyo"]
```

```typescript:ts
function log(msgs: string[]): void {
  msgs.push("piyo")
  console.log(msgs)
}

const msgs = ["hoge", "fuga"]
log(msgs)
> [ "hoge", "fuga", "piyo" ]
log(msgs)
> [ "hoge", "fuga", "piyo", "piyo" ] 
```

### 無名関数
Pythonの`lambda`は引数に型を書くことが出来ないのですが、TSはアロー関数の引数にも型をつけることが出来ます(実際には型推論が優秀であまり書くことはないですが)。

```pyx:py
lambda x: print(x)
```

```typescript:ts
(x: string) => console.log(x)
```

高階関数との組み合わせ↓

```pyx:py
seq = [1, 2, 3, 4, 5]
print(list(filter(lambda x: x % 2 == 0, seq)))
> [2, 4]
```

```typescript:ts
const seq = [1, 2, 3, 4, 5]
console.log(seq.filter((x) => x % 2 === 0))
> [ 2, 4 ]
```

## nullとundefined
### どっち使えばいい？
MSのTypeScriptチームは[**`null`を使わないそうです**](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#null-and-undefined)。

### 実際のところ
上記に倣って`null`は使わない、ただしnullチェックは`if (tgt != null)`で行うというプロジェクトが多いみたいです(`==`は`null`と`undefined`を区別しないため)。またこれがTSで`==`演算子を使う唯一の機会でもあります。

### nullにまつわる構文
最近のTSはかなりnull周りが書きやすくなっている印象があります。
以下の構文は両方ともPythonには存在しません。

```typescript:ts
// オプショナルチェーン
function len(text?: string): void {
  const len = text?.length  // textがundefinedの場合undefinedになる
  console.log(len)
}

// Null合体代入演算子
function echo(text: string, options: { big?: boolean; trim?: boolean }): void {
  options.big ??= true  // bigがundefinedの場合trueを代入
  options.trim ??= true
  let processed = text
  if (options.big) processed = processed.toUpperCase()
  if (options.trim) processed = processed.trim()
  console.log(processed)
}
```

## 列挙体(enum)とunion型
これもどちらを使うべきかという議論がありますが、私が調べた限り
[**さようなら、TypeScript enum | Kabuku Developers Blog**](https://www.kabuku.co.jp/developers/good-bye-typescript-enum)
これが結論なように見えました。

ちなみに[**ここ**](https://qiita.com/nicco_mirai/items/5591c1c48b1422c324c9#-literal)で触れた通りPythonのLiteral型は反復処理ができないため全てのEnumを駆逐するまでには至りません(頑張ってこねこねすればいけそうですが…)。

# クラス
## 定義
```pyx:py
from dataclasses import dataclass

@dataclass
class Player:
    name: str
    age: int

    def rename(self, name: str) -> None:
        self.name = name

me = Player("nicco", 29)
```

```typescript:ts
class Player {
  constructor(public name: string, public age: number) {}

  rename(name: string): void {
    this.name = name
  }
}

const me = new Player("mirai", 14)
```

Pythonの`@dataclass`はフィールドの宣言を元に`__init__`やその他特殊メソッドを自動で生成してくれるデコレータですが、TSでは逆にコンストラクタの引数にアクセス修飾子をつけることによってフィールドの宣言を省略することができます。
またメソッド内でインスタンス変数にアクセスするための`self`はTSでは`this`になります。

## 公称型と構造的部分型

PythonとTSはどちらも公称型と構造的部分型の両方を扱うことのできる言語です。
ただしPythonは**構造的部分型も扱うことが出来る公称型がベースの言語**であり
TSが**公称型も扱うことが出来る構造的部分型がベースの言語**であることは押さえておく必要があります。

それはなぜか。我々のような公称型に慣れた人間は公称型に準じた設計をしがちであり、そして構造的部分型はその影響範囲が広いからです。

参考:
[**TypeScript: 異なる2つの型システム「公称型」と「構造的部分型」**](https://qiita.com/suin/items/52cf80021361168f6b0e)

### 公称型に準じた設計とは
まさに上で書いたようなクラスがそれです。これを構造的部分型に準じた設計に変えてみましょう。

```typescript:ts
interface Player {
  name: string
  age: number
}

// `Player`型を含め`name`属性を持った型ならなんでも受け取れる
function rename(named: { name: string }, newName: string) {
  named.name = newName
}

const me: Player = { name: "mirai", age: 14 }
rename(me, "hikari")
```

こうです。Goでよく見た感じになりましたね。
[**Protocol**](https://qiita.com/nicco_mirai/items/5591c1c48b1422c324c9#-protocol)を継承しないと構造的部分型にならないPythonと異なり、TSで定義した型[^class]は特定の条件を満たさない限り常に構造的部分型です。
今後`{ name: string, age: number, rename: (name: string) => void }`が全て公称型に準じた型である`Player`と見なされてしまうのは設計者の意図に反する危ない状態ではないでしょうか？

### じゃあどうするのが正解？
どちらかに寄せましょう。構造的部分型に準じた設計に変えるか、クラスを公称型で定義するかです。
後者のやり方は単純で、1つ以上パブリックではないフィールドを作ればOKです。

```typescript:解決策その2.ts
class Player {
  constructor(private name: string, public age: number) {}

  rename(name: string): void {
    this.name = name
  }
}

class Neko {
  constructor(private name: string, public age: number) {}

  rename(name: string): void {
    this.name = name
  }
}

let player: Player
player = new Neko("mirai", 2)  // 代入できない！
```

参考:
[**公称型クラス | サバイバルTypeScript**](https://book.yyts.org/reference/object-oriented/class/class-nominality)

# おわりに
[**タイムリープTypeScript 〜TypeScript始めたてのあの頃に知っておきたかったこと〜**]
(https://qiita.com/advent-calendar/2021/timeleap-typescript)
カレンダー2日目の記事でした。
あの頃に知っておきたかったということでかなり基本に寄った内容でまとめてみましたがどうでしょうか？
特に環境周りは調べていて昔より随分楽になったんだな～と感じました。

また入門者の方はこれで一巡他の入門記事が読めるようになったのではないでしょうか。
おすすめは@uhyo氏の[**TypeScriptの型入門**](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a#fnref2)シリーズです。
本当にためになるシリーズなので未読の方はぜひ。



[^local]: グローバルに入れることもできますがnvmやらnpmを使っている意味が分からなくなるのでやめましょう
[^else]: というかアレが珍しすぎますね
[^scope]: 正確に言えば`let`と`const`は、ですが`var`は使わないので事実上
[^class]: TSはクラスを定義すると同時に型も定義されます