Sync from Qiita

キータで書いた記事を載せています

Hyperappを理解する

1. はじめに

Hyperappでタスク管理アプリの開発をし、 ある程度理解する事ができたので、思いのままに語ります。

1-1. I love Hyperapp

React,Reduxを愛しState管理以外は考えられない私。 2018年、Hyperappと出会いました。 なんというシンプルさでしょう。 まさに、この1KBのソースコードに出会うため 血と汗を流しながらシステムエンジニアを続けてきたと言っても過言ではありません。 React,Reduxの公式ページを頑張って読んでいた頃が懐かしい。 今はHyperappの1KBのソースを何度も読み返すのです。 是非Hyperappについて語り合いましょう。

1-2. 基本的な考え方を知っておく

State管理が基本です。

先ず、State管理によるDOM更新を考えた天才に、改めて拍手を送りたい👏 おかげで本当に楽しくコーディングが出来るようになりました。

データをStateと言う「状態」として一元的に扱い、 それが必ずDOMに反映される仕組み、なんと素晴らしいシンプルな考え方だ。 ブラウザ上で鮮やかに実行されるDOM更新、まさに芸術的🎨

このState管理がHyperappを使う上で知っておくべき基本的な考え方です。

一言で言うと、 hello worldと描画したければ、Stateをhello worldにしなはれ、 DOMのwidthを100pxに変更したいなら、Stateを100pxに変更しなはれ言う事です。

Stateは 例えるならばお料理の献立レシピです。 私の大好きな献立

主食:コメ(玄米) 主菜:ハンバーグ(牛100%, 手作りデミグラスソース) 副菜:サラダ(豆やレタスなど)

これを奥さんに渡すと晩御飯がテーブルに並びます。 ここで大事なルールがあります。 出てきた料理に勝手に直接手を加える事は禁止です。 例えばハンバーグに市販のケッチャップをかけるとか。 ケチャップが欲しいなら、献立レシピをケッチャップに変更して再度ワイフにお願いするのです。 するとワイフは魔法のごときスピードでトマトを煮てケチャップを作り出し、 あっと言う間に手作りケチャップがかかっています。 ご飯ではなくパンにしたい場合も同様。 つまり、DOMをHyperapp以外の方法で直接いじるな言う事です。 レシピに書いてない変更を加えるのはご法度。 上記のレシピをStateぽく表現するとこんな感じです。

{
  name: 'dinner',
  attributes: {type: 'western', volume: 'large'}
  chilidren: [
    {name: 'staple', attributes{type: 'original'}, children:['rice']}
    {name: 'mainDish', attributes{sauce: 'demi'}, children:['hanburge']}
    {name: 'sideDish', attributes{sauce: 'italian'}, children:['bean', 'tomato'...]} 
  ]
}

StateはJSONオブジェクトです。データを構造的に管理します。 そして、Stateの中のどの値をどのDOMに反映させるかは、 一般的なMVCフレームワークのviewテンプレートと同じノリで書けます。

描画の拠り所となるデータ群がStateと言う一箇所に集約され、 常に整理整頓された状態を保ちながら実装が出来るのです。

さあ、npmが入っていれば実行環境が整います。お好きなエディタで 衝動的に無計画に右脳で書きまくるのです。 見た目と動きを思いのままに実装すれば、右脳コーディングドリブンで、 データレイアウトは自然と決まっていくのです。 なんと楽しいフレームワーク

2. 具体的に見ていく

State管理について長くなり恐縮です。 ここからはもう少し具体的な内容です。

2-1. State, View, Action

これがHyperappを使いこなす上で知っておくべき フレームワークの要素です。 昔はMVCフレームワークなんて言葉をよく耳にしましたが、SVAですね。

要素 役割
State 前述の通り。Model的なデータ
View 画面に表示する見た目部分の定義情報
Action User操作からState更新処理を行う部分。画面上操作をStateに反映させる処理部分

公式Github 公式ページ記載されているシンプルなサンプル

import { h, app } from "hyperapp"

const state = {
  count: 0
}

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

const view = (state, actions) => (
  <div>
    <h1>{state.count}</h1>
    <button onclick={() => actions.down(1)}>-</button>
    <button onclick={() => actions.up(1)}>+</button>
  </div>
)

app(state, actions, view, document.body)

当たり前の事ですが、この3要素すべてjavascriptの変数(JSONオブジェクト)です。 state, actionsは見ての通り、ザ・JSONオブジェクトなので良いとして、 これから始める方は、viewで記述されている、一見HTMLのようなソースである、JSX syntaxがjavascriptには見えないと思います。でもこれも実態は結局jsの変数(JSONオブジェクト)になります。

各ポイントを説明

import { h, app } from "hyperapp"

ソース上ではh関数は使われていない様に見えますが、これは次の項で触れます。

actions

JSONオブジェクトの中で関数が定義されています。 これはユーザ操作(DOMのイベントハンドラ)と紐づけるべき処理たちです。 最新のStateをreturnしてStateの更新を行なっています。 アロー関数と言うsyntaxなので見慣れていない人は気持ち悪いかもしれませんが、 babelでトランスパイルして見慣れたsyntaxに変換してみると普通の関数です。

Hyperappの中でこのactionsの関数をベースに関数を再定義しています。 ここの部分です。 https://github.com/jorgebucaran/hyperapp/blob/master/src/index.js#L113

view

HTMLの様な物を記述している様に見えますが、HTMLっぽく見えるjsのsyntaxというだけの事です。 実際、view変数に入ってくる値はJSONオブジェクト(VirtualDOM)です。

app(state, actions, view, document.body)`

このapp関数がHyperappの処理を実行するjavaでいうmain関数的なものです。

この記事を読むのを即刻やめて、上記サンプルコードを実行しながら、 Hyperappのソースを頑張って読むのが、もっとも確実にHyperappを理解する方法です。 Hyperappソース

ですが、以降でapp関数とh関数、私なりに解説します。

2-2. h関数

私がHyperappの好きなところは、 Hyperappは関数を2つ提供します。以上。と言うところ。 関数名はシンプル。 happ

hから見ていきます。

h(nodeName, attributes, children)

nodeNem: String (DOMのnode名です。) attributes: JSON (id, class、onclickなどのDOMの属性情報) children: Array (h関数で作成したJSONオブジェクトのVirtualDOM、または単純な文字列を持つArray)

このh関数の役割はDOMの情報をJSONオブジェクトで表現する事です。 なぜなら、Hyperappは描画するDOMに関する情報を すべてJSONオブジェクトで持っているからです。

繰り返しですがViewと呼ばれている、JSX syntaxで表現されたVirtualDOMは、 JSONオブジェクトです。

2-2-1. DOM ➡︎ JSONオブジェクト

DOMはidやclassなどの属性、そしてchildren(タグ内にあるDOM) を持っています。例えば、

<div id="father">
  <main id="son">
    Boy
  </main>
</div>

このようなDOMをJSONオブジェクトで表現すると、こうです。

{
  nodeName: "div",
  attributes: {id: "father"},
  children: [
   {
     nodeName: "main",
     attributes: {id: "son"},
     children: [
       "Boy"
     ],
     key: null
   }
  ],
  key: null
}

h関数は、3つの要素(nodeName, attributes, children)を引数に 渡せば、上記のようなJSONオブジェクトを作って返してくれるのです。

2-2-2. JSX から h関数に

前項で紹介した公式Githubサンプルソースでは、h関数は見当たりませんでしたが、hyperappで実装したソースコードはトランスパイルしてからブラウザに読ませる必要があります。 babelでソースをトランスパイルすると、JSXで定義された情報がh関数で表現されます。

const view = (state, actions) => (
  <div>
    <h1>{state.count}</h1>
    <button onclick={() => actions.down(1)}>-</button>
    <button onclick={() => actions.up(1)}>+</button>
  </div>
)

このJSXをトランスパイルすると

return h("div", null, [
           h("h1", null, [state.count]),
           h("button", { onclick: function onclick() {
                           return actions.down(1);
                         } 
                       }, ["-"]),
           h("button", { onclick: function onclick() {
                           return actions.up(1);
                         }
                       },["+"])
       ])

このように変換されます。 h関数が使われていますね。 上のソースの様に自分でh関数を書いてもOKです。 実際h関数で書く必要があるケースは開発してるとあります。

babel トランスパイル試す このページの左メニューで、PLUGINにbabel-plugin-transform-h-jsxを追加すると試せます。

h関数に関してまとめると、

① VirtualDOMの実態はJSONオブジェクトで、これがないとhyperappはDOMを描画できません、 ② そして、h関数はVirtualDOM(JSONオブジェクト)を作ってくれる関数で、 ③ JSX syntaxはh関数をHTMLっぽく書く為のsyntaxと言う事です。

2-3. app関数

app(state, actions, view, DOM)

これまで説明してきた3要素の、state, view, actionsとリアルDOMを引数にとっています。

app関数の中では、ザックリ2つの事をしています。

① actionsの中にある関数のオリジナルの処理を保ちつつ、「Stateが更新された場合に再描画する」という処理を追加して関数を再定義してます。 ↓ここでやってます。 https://github.com/jorgebucaran/hyperapp/blob/master/src/index.js#L113

② 第4引数のリアルDOMの中に、viewで定義したVirtualDOMをリアルなDOMにして展開します。

そして①で再定義したactionsをreturnしています。ですので、actionをHyperappのライブラリの外からも実行する事が可能です。