Hyperapp V2 alpha → 安定版の変更点

[Hyperapp](https://github.com/jorgebucaran/hyperapp)の最新バージョンであるV2について、先日ついに安定版である2.0.0および2.0.1がリリースされました :tada:

ですが現時点では、alphaから安定版までにどのような点が変更されたのかについて、まとまったドキュメントが存在しないようです。
そこで、私自身がHyperapp V2のソースコードや[GitHubレポジトリのIssues](https://github.com/jorgebucaran/hyperapp/issues)などを読んで調査した結果を元に、安定版での変更点を簡単にまとめます。

なお、V1からV2で変更された点やalpha版時点でのAPIについては、@diontoolsさんの下記記事に分かりやすくまとめられているため、そちらの記事を参照してください。

- [Hyperapp V2](https://qiita.com/diontools/items/074dd32f86ecd995aff5)

# 1. Actionの引数の受け取り方が変わった

Actionが取れる引数が、最大3つではなく2つになり、alpha版のように「イベント引数などの内部的に自動で渡されるデータ（`data`）」と「Action呼び出し時に指定する引数（パラメータ）」を同時に指定することはできないようになりました。

```ts
// alpha
const Increment = (state, data) => ...;  // イベント引数などの内部データのみ受け取る
const Increment = (state, { value }) => ...;  // パラメータのみ受け取る
const Increment = (state, { value }, data) => ...;  // イベント引数、パラメータを両方受け取る

// 安定版
const Increment = (state, data) => ...;  // イベント引数などの内部データのみ受け取る
const Increment = (state, { value }) => ...;  // パラメータのみ受け取る
```

代わりに、__Payload Creator__という仕組みが用意されました。
これは、Actionの呼び出し時にパラメータではなく関数を渡すことで、内部データ（上記`data`）を元にパラメータを生成し、そのパラメータをActionに渡すことができるというものです。

たとえば、テキストボックスの値を変更した際に、その入力値（value）をActionの処理で使用したいという場合、下記のように書くことで実現することができます。

```jsx
<input onchange={[Increment, (e) => ({value: e.target.value})]} />
```

なお、この変更に伴い、Actionの引数関連の用語が下記のように整理されました。

- Actionが受け取る引数：__Payload__ 
- イベント引数などの内部データ(`data`)：__Default Payload__
- ユーザーがAction呼出時に渡す引数（パラメータ）：__Custom Payload__

```jsx
const Increment = (state, payload) => ...;   // この第2引数が "Payload"

<input onchange={Increment} />         //=> Incrementの第2引数にDefault Payload（イベント引数）が渡される
<input onchange={[Increment, 10]} />   //=> Incrementの第2引数にCustom Payload（10）が渡される
<input onchange={[Increment, (e) => e.target.value]} />   //=> Incrementの第2引数に、Payload Creatorで生成したCustom Payload（e.target.value）が渡される
```




# 2. app関数の引数が変わった

下記2点が変更されました。

- 引数名の`container`が`node`に変わった（機能は同じ）
- 新しく`middleware`を指定できるようになった

```jsx
// alpha
app({
  init: { value: 0 },
  view: state => <div>{state.value}</div>,
  subscriptions: state => ...,
  container: document.body,
});

// 安定版
app({
  init: { value: 0 },
  view: state => <div>{state.value}</div>,
  subscriptions: state => ...,
  node: document.body,
  middleware: ...
});
```

`middleware`は省略可能な引数で、この引数に関数を渡すことで`dispatch`関数（Action, Effectの実行時や新しいStateの設定時に使う関数）の処理を横取りし、前後に処理を追加することができます。

使い方はさまざまですが、一番使いそうなのはhyperapp V1にあったloggerのように、Action実行時のログ出力処理を行いたい場合でしょう。

```jsx
const SimpleLoggerMiddleware = (baseDispatch) => {
  // Middlewareは、標準のdispatch関数を引数として受け取り、新しいdispatch関数を返す必要がある
  return (action, props) => {
    console.group("Dispatch Log");
    console.log("Target: ", action);
    console.log("Props: ", props);
    console.groupEnd

    baseDispatch(action, props); // 標準のdispatch関数を実行
  }
}

app({
  init: { value: 0 },
  view: state => <div>{state.value}</div>,
  subscriptions: state => ...,
  node: document.body,
  middleware: SimpleLoggerMiddleware 
});
```


# 3. Effect Runner, Subscription Runnerが受け取る引数の順番が逆になった

第1引数が`dispatch`、第2引数が`props`の順になりました。

```jsx
// alpha
const delayRunner = ({ action, interval }, dispatch) => {
  setTimeout(() => dispatch(action, 'delay!'), interval);
};

// 安定版
const delayRunner = (dispatch, { action, interval }) => {
  setTimeout(() => dispatch(action, 'delay!'), interval);
};
```

# おわりに

この中では1のAction引数の変更が特にインパクト大きめですが、これによりalpha版よりも引数の受け取り方が分かりやすくなったように感じられました。

もし当記事の内容に誤りや不足があれば、コメントや[Twitter](https://twitter.com/tetradice)などで教えてください :pray: