Last updated at
info
この記事は最終更新から1年以上経っています。情報が古くなっている可能性があります。
この記事はTypeScript2.xを使用して、Hyperappを使ったアプリケーションを書くためのドキュメントである。
HyperappのドキュメントにはTypeScriptでの使い方が書いていないため書いた。 まぁTS型定義のテストファイル見れば使い方わかるんだけどね...
この記事で書かれているコードの確認環境は以下の通り
また、仮想ノードの構築にはJSX(.tsx
)を用いる。
まず最初にstateの型を定義する必要がある。 単純にstateの型をそのまま書いてやればよい。
interface State {
count: number
}
const state: State = {
count: 0
}
次にactionsの型を定義する。 各actionの型は「入力を受け取ってstateを返す」関数として定義する。
interface Actions {
increment(): State
decrement(value: number): State
}
そして定義した型を基にactionsを実装する。
実装側はActionsType
型を使うため、input? => (state?, actions?) => result
となるようにする。
result
の部分の型定義は公式の型定義ファイルを参照してね。
import { ActionsType } from 'hyperapp'
const actions: ActionsType<State, Actions> = {
increment: () => (state, actions) => ({ count: state.count + 1 }),
decrement: (value: number) => (state, actions) => ({ count: state.count - value }),
}
...しかし、残念なことに現時点では定義したActionsの型情報があまり役に立たたない。
// このように書いてもTSのコンパイルは通ってしまう
const actions: ActionsType<State, Actions> = {
increment: (value: number) => (state, actions) => ({ count: value }),
decrement: (value: string) => (state, actions) => ({ count: value })
}
actionsの実装に於いてはアクション名のチェック程度にしか使えないと考えておいたほうがよい。
ロジックの次はviewを書く。特に難しいことは無い。
import { h, View } from 'hyperapp'
const view: View<State, Actions> = (state, actions) => (
<main>
<button onclick={() => actions.decrement(1)}>-</button>
<span>{state.count}</span>
<button onclick={() => actions.increment()}>+</button>
</main>
)
ジェネリクスパラメータで指定しているState
とActions
がここではちゃんと活きている。
// これはコンパイルエラーになる
const view: View<State, Actions> = (state, actions) => (
<main>
<button onclick={() => actions.decrement('1')}>-</button>
<span>{state.count}</span>
<button onclick={() => actions.increment(1)}>+</button>
</main>
)
最後にstate, actions, viewをがっちゃんこする。 ジェネリクス指定してあげる以外はTS的な要素はない。
import { app } from 'hyperapp'
app<State, Actios>(
state, actions, view, document.getElementById('app')
)
ここでいう「モジュール化」とはstateとactionsをグループ化することであり、ESModulesのことではない。
直感的にそのまま分ければ大丈夫。
// modules/counter.ts
import { ActionsType } from 'hyperapp'
export interface State { count: number }
export const state: State = { count: 0 }
export interface Actions {
increment(): State
decrement(value: number): State
}
export const actions: ActionsType<State, Actions> = {
increment: () => state => ({ count: state.count + 1 })
decrement: (value: number) => state => ({ count: state.count - value })
}
// index.ts
import * as Count from './modules/counter'
interface State { count: Count.State }
const state: State = { count: Count.state }
interface Actions { count: Count.Actions }
const actions: ActionsType<State, Actions> = {
count: Count.actions
}
これでstate.count.count
やactions.count.increment
としてアクセスできるようになる。
コンポーネントは至ってシンプル。
// components/Counter.tsx
import { h, Component } from 'hyperapp'
interface Props {
count: number
onchange(v: number): any
}
const Counter: Component<Props> = ({ count, onchange }) => (
<div>
<button onclick={() => onchange(count - 1)}>-</button>
<span>{count}</span>
<button onclick={() => onchange(count + 1)}>+</button>
</div>
)
export default Counter
ただ、このまま書いていくとpropsのバケツリレーが起きてしまう。そのため、Lazy Componentsという機能を使い、StateとActionsを直接コンポーネントに渡して冗長さを軽減することができる。
// ...
import { State, Actions } from '../'
// Component<Props, State, Actions>なので、propsを受け取らない場合は{}を指定する
const Counter: Component<{}, State, Actions> = () => (state, actions) => (
<div>
<button onclick={actions.count.decrement}>-</button>
<span>{state.count.count}</span>
<button onclick={actions.count.increment}>+</button>
</div>
)
// ...
以上Hyperappの各要素にフォーカスした書き方でした。
実際のサンプルが見たい場合はTypeScript+Hyperapp(+CSS Modules) build with Webpackなサンプルプロジェクトを用意しているので、そちらを見てね。