フロントエンド初心者がHyperappに触れてみる

hyperappを使って公式に載っているごく簡単なカウンターアプリを作りました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/a5d3384e-1f24-638b-904e-3c24d4fe4f42.png)

## 開発環境の構築

フロントエンドわからないマン（reactの本と公式チュートリアル１周した程度）なのでとにかく楽に導入できる方法を選択しました。

```powershell
# 作業フォルダの設定
PS> mkdir try-hyperapp
PS> cd try-hyperapp

# package.jsonの作成
PS> yarn init -y

# Hyperappのインストール
# babel-preset-envのインストール（環境に合わせてトランスパイルしてくれる？）
# parcelのインストール（設定ファイルのいらないモジュールバンドラー）
PS> yarn add hyperapp babel-preset-env parcel-bundler
```

最終的な`package.json`は以下のようになりました。

```json:package.json
{
  "name": "try-hyperapp",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "babel-preset-env": "^1.6.1",
    "hyperapp": "^1.2.5",
    "parcel-bundler": "^1.8.1"
  },
  ※↓TypeScriptで書いたのでparcelでバンドルした際に自動で追加されたようです。
  "devDependencies": {
    "typescript": "^2.8.3"
  }
}
```

TypeScriptで書きたいので`tsconfig.json`を作成します。

```json:tsconfig.json
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "sourceMap": true,
    "strict": true
  }
}
```

## `counter.tsx`の作成
まず、ルートディレクトリ直下に`src`フォルダを作成します。
本当なら`actions`フォルダや`views`フォルダに分けたほうが良いのかもしれませんが、今回は簡単に`src`直下に全てまとめた`counter.tsx`を作成します。

```tsx:src/counter.tsx
import { h, app, ActionsType, View } from "hyperapp";

interface IState {
  count: number;
}
const state: IState = {
  count: 0
};

interface IActions {
  down: (value: number) => IState;
  up: (value: number) => IState;
}
const actions: ActionsType<IState, IActions> = {
  down: value => state => ({ count: state.count - value }),
  up: value => state => ({ count: state.count + value })
};

const view: View<IState, IActions> = (state, actions) => (
  <div class="card center-align">
    <div class="card-image">
      <h1>{state.count}</h1>
    </div>
    <div class="card-action">
      <button
        class="waves-effect waves-light btn-large"
        onclick={() => actions.down(1)}
      >
        sub
      </button>
      <button
        class="waves-effect waves-light btn-large"
        onclick={() => actions.up(1)}
      >
        add
      </button>
    </div>
  </div>
);

app<IState, IActions>(state, actions, view, document.body);
```

`ActionsType`や`View`はTypeScriptでもインポートしなくてもコンパイラに怒られませんが、型が定まっているほうがうっかりミスが減ったり補完が効くので導入しました。

`actions`の`value`が`any`型になってしまい、若干不安でしたが、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/ab014f97-6cd1-58ff-a271-41e0d00811ce.png)

使用する際にはインターフェースの通り`number`型と推論されていたので安心しました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/c6dd4159-feee-f1a7-7db4-811afd0aa554.png)


## Index.htmlの作成

parcelのエントリーポイントになる`Index.html`をルートディレクトリ直下に作成します。
見た目がさみしいのでCDN経由で`Materialize.css`を使います。

```html:index.html
<!DOCTYPE html>
<html lang='ja'>

<head>
  <meta charset='UTF-8'>
  <title>try hyperapp</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
</head>

<body>
  <script src='./src/counter.tsx'></script>
</body>

</html>
```

## 実行

以下のコマンドで`dist`フォルダが作られてローカルサーバー立ち上がります。
今回、TypeScriptを使いましたが、インストールされていない場合、自動で行ってくれるようです。

```powershell
PS> yarn run parcel index.html
```

## 感想とわからなかったところ

#### 良かったところ

- `redux`のような`elm`アーキテクチャ？が最初から組み込まれているのが`react`と比べて好印象。
- `action`周りが簡潔なところも好きです。
- TypeScriptと相性がよさそう。
    - 補完がバリバリ効いてうっかりミスもなくなる！
- ボイラープレート的なコードが少なく、やりたいことが簡潔に書ける

#### 分からなかったところ

- フォルダ構成のベストプラクティス
   - ちょっと大きめのアプリを作成する際は`react`のDucksパターンみたいのがいいのでしょうか？
 
```
src
.
|_ views
| |_ counter.tsx
| |_ foo.tsx
|_ states
| |_ counter.ts
| |_ foo.ts
|_ actions
  |_ counter.ts
  |_ foo.ts
```

- `actions`や`state`の型は`interface`がいいのか`type`がいいのかわからない
   - 困っているので詳しい方いらっしゃれば教えてください:bow_tone1: 

```ts
// interfaceで書く
interface IActions {
  down: (value: number) => IState;
  up: (value: number) => IState;
}
const actions: ActionsType<IState, IActions> = {
  down: value => state => ({ count: state.count - value }),
  up: value => state => ({ count: state.count + value })
};

// typeで書く
const actions: ActionsType<IState, IActions> = {
  down: value => state => ({ count: state.count - value }),
  up: value => state => ({ count: state.count + value })
};
type Actions = typeof actions;
```

- デバッグ方法が分からない:scream:
   - vscodeでステップ実行する方法が分からないのでよく調べなければいけない・・・
   - `sourcemap:ture`にしたりvscodeにchrome開発者拡張入れたりしたけどダメでした