PHPの視点でTypeScriptを勉強してまとめてみました

普段PHPとJavaScriptを使って開発していますが、TSを触る機会が増えたので勉強したものをまとめてみました。

# Primitive タイプについて

### PHPとTSが存在するprimitive型

| PHP | TypeScript |
|:-:|:-:|
| int  | number  |
| float  |  number |
| string  | string  |
| bool  |  boolean |
| Undefined variable エラーになる  | undefined  |
| mixed | any |
| void | void |

objectとarrayについては下にまとめます。

### 変数定義時の型定義

```php:PHP
// 通常の変数定義
$a = 5.5;

// このように定義するとSyntaxエラーが起きる
float $a = 5.5; 
```

```ts:TypeScript
const myStr: string = "";
const myNullableStr: string|null = null;
const implicitVal = 5.5; // 自動的にnumber型になる

// typeを使った定義方法
type MyStr = string;
const myStringWithType: MyStr = "";
```

PHPはTSのように変数定義時に型をつけることができません。関数やクラスなどの定義する場合のみにしか型がつけれられません。

# 関数について

### 関数の型定義

```php:PHP
function isEmpty(string $a): bool {
  return strlen($a) == 0;
}
```

```ts:TypeScript
const isEmpty = (a: string): boolean => {
  return a.length == 0;
}
```

### Nullableの定義

```php:PHP
function isEmpty(?string $a): bool {
  return strlen($a) == 0;
}

// もしくは

function isEmpty(string $a = null): bool {
  return strlen($a) == 0;
}
```

```ts:TypeScript
const isEmpty = (a?: string): boolean => {
  return a?.length == 0;
}
```

### PHP8から始まる結合型 (Union Type)


```php:PHP
function myFunc(string|int|float $a) {}
```

```ts:TypeScript
const myFunc = (a: string|number) => {}
```

### TS特有のtype predicateについて

PHPにない機能ですが、TSでは引数で定義している型と違う型に上書きすることができます。用途として引数に複数の型が存在してフィルターしたり、特定の型を抽出したい場合に役に立ちます。

例として、nullや数字など混合した配列から２文字のアルファベットを抽出したい場合。

```ts:TypeScript
const list = ["A","BC",null, 1, "DEF"];

const isTwoAlphabets = (val: any): val is string => {
  // TSはval引数をstringとして認識するが、実際はnullや数字などの可能性があるため
  // typeofを使ってstring型を抽出する必要がある
  if (typeof val !== 'string') {
    return false;
  }

  // 型の抽出が終わったら安全にTSの指示通りに書くことができる
  return !!val.match(/^[A-Za-z]{2}$/);
}

const result = list.filter(val => isTwoAlphabets(val));
// 結果は ["BC"] 
```

# Object と Class について

PHP8から使える constructor promotion を使って次のように表現します。

```php:PHP
class Circle {
  public function __construct(
    public int|float $radius
  ) {}
}

class Color {
  public function __construct(
    public string $name,
    public ?string $comment = null
  ) {}
}

class ColorfulCircle {
  public function __construct(
    public Circle $circle,
    public Color $color
  ) {}
}

$myCircle = new Circle(5);
$red = new Color("red");
$myRedCircle = new ColorfulCircle($myCircle, $red);
```

tsの場合は次のようにobjectの型定義ができます。

```ts:TypeScript
type Circle = {
  radius: number;
};

type Color = {
  name: string;
  comment?: string;
};

type ColorfulCircle = {
  color: Color;
  circle: Circle;
}

const myCircle: Circle = {
  radius: 5
};

const red: Color = {
  name: "red",
};

const myRedCircle: ColorfulCircle = {
  circle: myCircle,
  color: red
};
```

補足１、JavaScriptのObjectはPHPのclassのようにprivateやprotectedなど修飾子が存在しないため、定義したobjectのattributeが常にpublicになります。

補足２、TSでは`type`の記述の他に`interface`の記述方法もあります。

```ts:interafaceを使った記述
interface Circle {
  radius: number;
}

interface Color {
  name: string;
  comment?: string;
}

interface ColorfulCircle {
  color: Color;
  circle: Circle;
}
```

### Extendsと交差型について

PHPではextendsを使ってclassを拡張します。

```php:PHP
class ColorfulCircleService extends ColorfulCircle {
  public function draw(Circle $circle): void {
    print_r("Drawing"); 
  }
}
```

TSの場合は交差型を使って同じことを実現します。

```ts:TypeScript
type ColorfulCircleService = ColorfulCircle & {
  draw: (circle: Circle) => void;
};

const myRedCircle: ColorfulCircleService = {
  circle: myCircle,
  color: red,
  draw: (circle: Circle, color: Color) => {
	console.log(`Drawing ${color.name} ${circle.radius} circle`);
  },
};
```

TSではinterfaceを使った記述もあります。上記typeの記述と同じ内容になります。

```ts:interfaceを使う場合
interface ColorfulCircleService extends ColorfulCircle {
  draw: () => void;
}
```

# Array について

PHPが配列における型制御が少ないです。

```php:PHP
function drawAllCircle(array $circles): void {
  foreach($circles as $circle) {
    if (!($circle instanceof Circle)) {
        // $circleにCircle以外の可能性があるので、
        // ここでCircle型であることを確認しないといけない。
        throw new TypeError('');
    }

    // ここから安全に $circle を Circle として使うことをできる
  }
}
```

```ts:TypeScript
const drawAllCircle = (circles :Circle[]): void => {
  for (let circle of circles) {
      // ここから安全に circle を Circle として使うことをできる
  }
}
```

### TSでUnion型と配列を組み合わせて使う例

次のように配列に複数型の値を格納することができます。

```ts:TypeScript
type MixedDay = Date | null | string | undefined;

const days: MixedDay[] = [null, new Date, "2022-01-05"];
```

# 補足資料

### TypeScriptのPlayground
https://www.typescriptlang.org/play

### TSのclassを省いた理由

TSにもclassの記述がたくさんあります。ただ、個人として主にReact+TSを開発を進めているのと、Reactコンポーネントの主流がclassコンポーネントからfunctionalコンポーネントに変わることもあり、classを使う場面がほとんどなくなったので、classを省きました。

### 参考記事

https://qiita.com/Yamamoto0525/items/e790d6ecd62edf61d9e0
https://www.typescriptlang.org/cheatsheets/