Riot v4 で TypeScript がサポートされたので使ってみた

# 環境構築

[インブラウザ・コンパイル](https://riot.js.org/ja/compiler/#インブラウザコンパイル) では `TypeScript` が使えないので `webpack` などのローダーを使う必要があります。
サクッと環境構築するために、公式で用意されているサンプルをベースにしました。
https://github.com/riot/examples/typescript

## ダウンロード
https://github.com/riot/examples
[Clone or download] -> [Download ZIP]
<img width="382" alt="Download ZIP" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140892/7383651a-9589-d762-1ee9-38970772e62b.png">

すべてのexampleがダウンロードされるので、その中にあるtypescriptを使います。

## インストール

```
cd ./path/to/riot-examples/typescript
npm install
```
## 起動
```
npm start
```
ブラウザで↓を開くと乱数を生成するサンプルが表示されます。
http://localhost:3000/
<img width="550" alt="スクリーンショット 2019-11-30 11.52.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140892/b72c44be-0647-7b6c-dec9-2c4afe970f91.png">

# TypeScriptでのRiotコンポーネントの書き方

riot-example/typescript のコードを `JavaScript` の記述と比べながら確認してみます。

## random.riot のテンプレート部分

```html:random/random.riot
<random>
  <h3>{ props.title }</h3>

  <button onclick={ generate }>
    Generate
  </button>

  <h1>
    { state.number }
  </h1>

  <logs logs={ state.logs } onclear={ clearLogs }></logs>

  <script type="ts">
    // ...（省略）
  </script>
</random>
```

こちらは `JavaScript` で書く方式と同じですね。

## random.riot の Script 部分

```html:random/random.riot
<random>
  <!-- （省略） -->
  <script type="ts"> // (1)
    import Logs from '../logs/logs.riot'
    import {RandomComponent} from './types' // (2)

    function Random(): RandomComponent { // (3)
      return {
        state: {
          number: null,
          logs: []
        },
        generate(event: MouseEvent): void { // (4)
          this.update({
            number: Math.floor(Math.random() * 10000),
            logs: this.state.logs.concat({
              text: `Generate button clicked. Event type is ${event.type}`
            })
          })
        },
        clearLogs(): void {
          this.update({
            logs: []
          })
        }
      }
    }

    Random.components = {
      Logs
    }

    export default Random
  </script>
</random>
```

1. TypeScript であることを `type="ts"` で指定します。

2. 後述する型定義ファイルをインポートしています。

3. 一見ガラッと変わっているように見えますが、JavaScriptでもファクトリ関数を使ってコンポーネント生成ができるので同じです。
https://riot.js.org/ja/api/#ステートハンドリング

4. 引数と戻り値の型が指定できます。

## random.riot と対になっている types.ts

```typescript:random/types.ts
import {RiotComponentExport} from 'riot'

export interface RandomComponentState { // (1)
  number: number | null;
  logs: { text: string }[];
}

export interface RandomComponentProps { // (1)
  title: string;
}

export interface RandomComponent extends RiotComponentExport<RandomComponentProps, RandomComponentState> { // (2)
  generate(event: MouseEvent): void;
  clearLogs(): void;
  state: RandomComponentState;
}
```

`JavaScript` で記述する際には存在しない型定義ファイルで、コンポーネントのインターフェースが定義されています。

1. `props` と `state` のフィールドとそれぞれの型が指定できます。
2. メソッドの定義を指定できます。

# TypeScript の恩恵はどこで受けられるの？

ここが一番のポイントだと思うので色々試してみました。

## `number` 型で定義されている変数に文字列を入れる

```typescript
function Random(): RandomComponent {
  return {
    state: {
      number: 'riot.js', // <-- ここを変更
      logs: []
    },
    // ...
  }
}

// -------------------------------
// random.riot.ts
// Error 2322 : Type 'string' is not assignable to type 'number | null'.
```
アサインできない型なのでエラーになりました。

## `types.ts` にだけ関数を追加する

```typescript
export interface RandomComponent extends RiotComponentExport<RandomComponentProps, RandomComponentState> {
  generate(event: MouseEvent): void;
  clearLogs(): void;
  hoge(): void; // <-- ここに追加
  state: RandomComponentState;
}

// -------------------------------
// random.riot.ts
// Error 2741 : Property 'hoge' is missing in type '{ state: { number: null; logs: never[]; }; generate(event: MouseEvent): void; clearLogs(): void; }' but required in type 'RandomComponent'.
```

インターフェースが定義されているのに実装が存在していないのでエラーになりました。
`types.ts` の `state` にだけフィールドを追加した場合も同様の結果でした。

## `random.riot` にだけ関数を追加する

```typescript
function Random(): RandomComponent {
  return {
    // ...
    clearLogs(): void {
      this.update({
        logs: []
      })
    },
    fuga(): void { // <-- ここに追加
      // 何らかの処理
    }
  }
}
```

これはエラーにはならなかったです。
テンプレート部分から呼び出す場合は型定義を書いて、ローカル関数は書かないという使い方ができそうです。

## `.riot` ファイルからコード補完は効くのか

Visual Studio Code では効きませんでした。
Riot.js が HTML の中にスクリプトを書く形式なので仕方がないですね。

## 感想

型定義ファイルを書く分作業量は増えてしまいますが、インターフェースの恩恵を受けたいケースでは使えそうに思いました。