Hyperappに魅せられて

# 背景

WebアプリケーションのViewを扱うJavascriptライブラリは、2018年現在でも、まだまだ群雄割拠の時代が続いている。
いろいろ勉強してみたが、個人的な所感としては以下のような印象です。

## 実装が複雑になりがち

* 実装が複雑になり、書くコード量も多くなりがち
* 結局やりたいことは「ステート管理」「イベントハンドリング」「DOMを動的に変える」だけなのに...

## 学習コストが高い

* ライブラリ内部で何やってるか実装レベルではいまいちわからない
* 各ライブラリのバージョンが上がり、色々なことができるようになっているが、その分、学習コストが上がっている
* ドキュメントが長いので読む気が失せる

## 色々なライブラリを組み合わせなくては、一つのアプリケーションを作れない

* 様々なライブラリをたくさん組み合わせることで、便利になったり、色々なことができるようになるが、使うライブラリが多くなるため、アプリケーション自体が肥大化する
* 使うライブラリの数だけ学習コストが上がる
* 使うライブラリそれぞれに依存関係があると、例えば、Aのライブラリのバージョンアップすると良いことがあるが、BのライブラリがAのライブラリをバージョンアップすると使えなくなるので、Bのライブラリをアップデートしようとしたら、使っているアプリケーションを書き換えないと動かなくなるので、結局、Aのライブラリをバージョンアップできないみたいな状態が生まれる

そんな中、期待の新星として、2018年初頭に出てきた[Hyperapp](https://github.com/hyperapp/hyperapp/)。
ミニマリストでシンプル至上主義・複雑なことを勉強するのが億劫な自分には非常に興味深いライブラリでした。

※V1の内容です。

# コンセプト

公式に書いてあるコンセプトは以下の3つ。

* 最小限である
* 実用的である
* 独立している

---

要は、 ReactやVueなどで使われている

* Virtual DOM
* Lifecycle Events

の概念と、Redux や Vuexなどで使われている

* State Management（状態管理）

の概念を、
「最小限」で、「実用的」に、一つにまとめた（「独立している」）ライブラリ。

なので、それぞれがよくわかっていないとHyperappの何がすごいかがわからないかも。

# 容量

uglifyなど圧縮ツールを使わなくても、9.7kbのindex.jsファイル。
398行のソースコードです。

読もうと思えば簡単に読めるので、内部で何をしているかを実装レベルで把握することが比較的容易。

# 覚える関数

```h```関数, ```app```関数の2個だけ。

```h``` は virtual DOM node treeを返す関数。
```app```は特定のDOMに、作ったアプリケーションをmountする関数。

h関数だけでも、Virtual DOMを構築できるが、JSX記法も、Bunlderの設定をすれば対応可。

こんな感じ

```jsx
import { h, app } from 'hyperapp'

const state = {} // state
const actions = {} // actions
// component
const Sample = () => {
  return <h1>Hello World!</h1>
}
// view
const view = (state, actions) => {
  return <Sample />
}

const main = app(state, actions, view, document.body)
```

# Components

ReactやVueなどにもあるComponentという概念。

## props

ReactやVueでいうところの、props（プロパティ）を上の階層から渡せる。

```jsx
// component
const Sample = (props) => {
  return <h1>Hello {props.suffix}!</h1>
}
// view
const view = (state, actions) => {
  return <Sample suffix={'Hyperapp'} />
}
```

## children

子要素も渡せる。propsとは別。
Riot.jsでは、childrenを渡せなかった苦い記憶がある。

```jsx
// component
const Sample = (props, children) => {
  return <h1>{children}</h1>
}
// view
const view = (state, actions) => {
  return <Sample>Hello World!</Sample>
}
```

## Lifecycle Events

oncreate / onupdate / onremove / ondestroyが使用可能
他のライブラリに比べてできることは少ない印象だが、実はこれだけで実用的に十分なのでは？
個人的には、Component自体ではなく、DOM要素に対して定義できるのが最高。
Lifecycle eventsを定義するためだけに、わざわざComponent化したり、class化しなくていいので楽。

```jsx
const Sample = () => {
  return (
    <h1
      oncreate={(element) => { console.log('create') }}
      onupdate={(element, oldAttributes) => { console.log('update') }}
      onremove={(element, done) => { console.log('remove'); done() }}
      ondestroy={(element) => { console.log('destroy') }}
    >
      Hello World!
    </h1>
  )
}
```

※onremoveではdoneを呼ばないと、DOMが消えてくれない。

## Local State

ReactやVueと違い、Component自体に状態を持つことが「できない」。
そのお陰で、グローバルなstateに対するVirtual DOMを返す、純粋な関数となる。
状態を内部で保持しないというのが、関数型プログラミングっぽくて好み。

単体テストもしやすくなる。

```jsx
import { h } from 'hyperapp'
import { renderToString } from 'hyperapp-render'

const Sample = (props, children) => {
  return <h1>{children}{props.suffix}</h1>
}

// jest
test('view to match snapshot', () => {
  const sample = renderToString(<Sample suffix={'!'}>Hello World</Sample>)
  expect(sample).toBe('<h1>Hello World!</h1>')
})
```

# State

state（状態）は、単一のグローバルで、プレーンなオブジェクト。
かつ、一度作成されると変更できない(immutable)。
reduxのstore + immutable.jsっぽい感じ。

```javascript
const state = {
  count: 0
}
```

stateを更新するには、後述するactionsを使わなくてはならない。

もちろん、Nest（ネスト）させることもできる。

```javascript
const state = {
  counter: {
    count: 0
  }
}
```

# Actions

* stateを更新するには、actionsを使わなければならない。
* stateが更新されるたびに、再描画が行われる。
* stateは常にimmutable。
* actionsには、ひとつの引数を渡すことができる。
* actionsで、部分的な状態オブジェクト(partial state object)を返すと、それがグローバルなstateにshallow mergeされる。
* actionsで、現在のstateとactionsを引数にとり、部分的な状態オブジェクトを返す関数を返すことができる。
* actionsで、Promise / undefined（何も返さない） / nullが返された場合は、stateの更新・再描画は行われない
 * これを利用すると、非同期のactionによるstateの更新が可能になる（APIを叩くときなどに有用）

```javascript

const state = {
  count: 0,
  hoge: 0
}

const actions = {
  // state.countをvalueに更新
  setCounter: value => ({ count: value }),

  // state.countを現在のstate.countからvalueだけマイナスした値に更新
  down: value => (state, actions) => ({ count: state.count - value }),

  // state.countを現在のstate.countからvalueだけプラスした値に更新
  up: value => (state, actions) => ({ count: state.count + value }),

  // state.countを1秒後に現在のstate.countからvalueだけプラスした値に更新
  upTimeout: value => (state, actions) => {
    setTimeout(actions.up, 1000, value)
  },
}
```

## Nested Actions

stateは単一のグローバルオブジェクトだが、ネストされたstate(Nested state)とネストされたactions(Nested Actions)を組み合わせることで、このactionはstateのこの部分を変更する（この部分しか変更しない）ことを簡単に明示的に書ける。
このことで、stateの管理を分割統治できる。

```javascript
const state = {
  counter: {
    count: 0,
  },
  counter2: {
    count: 0,
  }
}

const actions = {
  // state.counter以下のcountを更新する
  counter: {
    setCounter: value => ({ count: value }),
    down: value => (state, actions) => ({ count: state.count - value }),
    up: value => (state, actions) => ({ count: state.count + value }),
  },
  // state.counter2以下のcountを更新する
  counter2: {
    setCounter: value => ({ count: value }),
    down: value => (state, actions) => ({ count: state.count - value }),
    up: value => (state, actions) => ({ count: state.count + value }),
  }
}
```

stateが増えてきたら、ファイルを分けるなど、モジュール分割もやりやすい。
reduxの```combineReducers```みたいなやつ。

```javascript:index.js
import counter from './module/counter'
import counter2 from './module/counter2'

const state = {
  counter: counter.state,
  counter2: counter2.state
}

const actions = {
  counter: counter.actions,
  counter2: counter2.actions
}
```

```javascript:module/counter.jsとmodule/counter2.js
const state = {
  count: 0,
}

const actions = {
  setCounter: value => ({ count: value }),
  down: value => (state, actions) => ({ count: state.count - value }),
  up: value => (state, actions) => ({ count: state.count + value }),
}

export default { state, actions }
```

HyperappではReduxでいうreducerやVuexでいうMutationの概念がない。
つまり、Nested Actionsで分割統治すると、actionが変更できる部分のstateは限定され、他のところに影響を及ぼさないことを保証できる。

reducerは、

* 一つのactionを追加するたびに、そのactionに対するreducerを定義しなくてはならない
* reducer自体の定義が肥大化しがち（switch case 地獄 or actionHandlers 地獄）
* ActionType指定とかの定義も面倒（他と被らないか気にしたり）
* reducerが他のモジュールのactionを受け取ってstateを更新していて、それがバグの原因になったりする
* actionでデータ加工するか、reducerでデータ加工するか、それともactionで渡す引数をデータ加工するかで迷う

と、やっぱつれぇわ（個人の感想です）と思っていたが、Hyperappだとシンプルに書ける。

ただ、その分、仕様・設計変更に柔軟に対応できる訳ではないので、stateとactionsを組み合わせたモジュール分割にはセンスが要る。
さらに、stateとactionsとcomponentを組み合わせて分割しようとすると、大変になるので要注意。
個人的には、stateとactionsはまだしも、state/actionsとcomponentとの間では、依存関係を作らない方がいいのではないかと思っている。

# Lazy Components

Componentは親Componentからpropsとchildrenだけしか受け取れないという仕様だと、グローバルなstateをバケツリレーしなくてはならず、Componentの階層が深くなるとカオスになる。

そこで、編み出されたのがLazy Component。
React + reduxでいうContainerのようなもの。

以下のように、```state```, ```actions```を引数にとる関数を返すことで実現できる。

```jsx
// state
const state = {
  counter: {
    count: 0,
  },
  counter2: {
    count: 0,
  }
}

// actions
const actions = {
  // state.counter以下のcountを更新する
  counter: {
    setCounter: value => ({ count: value }),
    down: value => (state, actions) => ({ count: state.count - value }),
    up: value => (state, actions) => ({ count: state.count + value }),
  },
  // state.counter2以下のcountを更新する
  counter2: {
    setCounter: value => ({ count: value }),
    down: value => (state, actions) => ({ count: state.count - value }),
    up: value => (state, actions) => ({ count: state.count + value }),
  }
}

// lazy component
const SampleContainer = (props, children) => (state, actions) => {
  return (
    <div>
      <h1>{state.counter.count}</h1>
      <button onclick={() => actions.counter.setCounter(2)}>SET 2</button>
      <button onclick={() => actions.counter.down(2)}>DOWN 2</button>
      <button onclick={() => actions.counter.up(2)}>UP 2</button>
    </div>
  )
}

const SampleContainer2 = (props, children) => (state, actions) => {
  return (
    <div>
      <h1>{state.counter2.count}</h1>
      <button onclick={() => actions.counter2.setCounter(2)}>SET 2</button>
      <button onclick={() => actions.counter2.down(2)}>DOWN 2</button>
      <button onclick={() => actions.counter2.up(2)}>UP 2</button>
    </div>
  )
}

// view
const view = (state, actions) => {
  return (
    <div>
      <SampleContainer />
      <SampleContainer2 />      
    </div>
  )
}

const main = app(state, actions, view, document.body)
```

React + reduxでは、Presentational componentとContainer componentに分けるのがベストプラクティスなので、同じように、lazy componentでは、普通のcomponentのpropsにstate, actionsを渡す実装が良さそう。

```jsx
// component
const SampleComponent = (props, children) => {
  const { state, actions } = props
  return (
    <div>
      <h1>{state.count}</h1>
      <button onclick={() => actions.setCounter(2)}>SET 2</button>
      <button onclick={() => actions.down(2)}>DOWN 2</button>
      <button onclick={() => actions.up(2)}>UP 2</button>
    </div>
  )
}

// lazy component
const SampleContainer = (props, children) => (state, actions) => {
  return <SampleComponent state={state.counter} actions={actions.counter} />
}

const SampleContainer2 = (props, children) => (state, actions) => {
  return <SampleComponent state={state.counter2} actions={actions.counter2} />
}
```

reselectみたいにSelectorを定義してやるとさらに良さそう。

# Hyperappでの開発に使えそうな便利ツール

* ルーティングしたい
  * [Hyperapp Router](https://github.com/hyperapp/router)
* stateの状態をデバッグしたい
 * [hyperapp-redux-devtools](https://github.com/andyrj/hyperapp-redux-devtools)
     * Redux Devtoolsをhyperappでも使えるようにするもの

# Hyperappの辛いところ(2018年現在)

* cssサポート
 * styled-componentを作りたい。cssと併用してclass指定して書きたいけどどうやってやるのか？
     * html/cssとjsの分業したい
 * React Storybookのように、Component単位で描画できるツールが欲しい

# まとめ

SPAで結局どういったことを実現しているのかをシンプルな設計によって実現しており、とても良いライブラリだと思いました。
cssとの連携周りがうまくいけば、最高のライブラリになりそうです。