モジュラーハイパーアプリ-パート6


NOTE: this article series is superceded by

最後の2つの部分では、私はアクションにたくさん集中しましたが、効果や購読を扱う方法については何も言いませんでした.言うまでもないが、完全性のために

購読
状態が変更されるたびに、HyperAppはsubscriptionsに提供するapp({...})プロパティを呼び出し、あなたのアプリケーションが応答する必要があるすべてのサブスクリプションの配列を返すことを期待します.
これは、HyperAppがどのようにDOMが見なければならないかについて調べるために州更新をするたびにビューを呼ぶ方法と類似しています.我々は非常に同様の方法でどのようにパート2でビューを破ったサブスクリプションを破ることができます.
const counterSubs = model => [
    onKeyDown('ArrowUp', model.Increment),
    onKeyDown('ArrowDown', model.Decrement),
]

//...

app({
    //...
    subscriptions: state => [
        ...counterSubs({
            Increment: IncrementFoo,
            Decrement: DecrementFoo,
        }),
        // other subs...
    ]
}) 

サブスクリプションのリストは、ビューとして、あるいはアクションとして一般的に速く成長しません.別のモジュール( 4 - 5 )で動作を開始するまでは、何もしないでください.
サブスクリプションがモジュールに移動されたアクションを必要とする場合は、サブスクリプションコンポーネントとしてサブスクリプションを中断し、同じモジュールに移動する必要があります.モジュール内のビューコンポーネントと同様に動作を受け取ります.このようにして、モデルとその内容はモジュールだけで知られる秘密になります.
import * from './counter.js'

const foo = counter.wire({/* getter, setter, et.c.*/})

app({
    //...
    subscriptions: state => [
        ...counter.subs(foo.model(state)),
        // other subs...
    ]
})

効果
効果はアクションから返されます、そして、あなたがモノラルからwireの機能にあなたの行動を移したときでさえ、それはあなたを無視する必要はありません.

const wire = ({getter, setter, onData}) => {

    const GetNewData = state => [
        setter(state, {...getter(state), fetching: true}),
        httpGet('https://example.com/data', GotData) // <-- 
    ]

    // this action doesn't need to be part
    // of a model since only GetNewData needs it.
    const GotData = (state, data) => onData(
        setter(state, {
            ...getter(state), 
            fetching: false,
            data,
        })
     )

     //...
}

エフェクトとマップ変換.
唯一の問題は、実際には、マップされた変換で実行する効果が必要です.
あなたがパート5から思い出すだろうように、写像されたトランスフォームは行動に類似していますが、イベントに応じて発送されません.一つのモジュールが他のモジュールのアクションからコールできるようにする関数です.
彼らの行動への類似性は、あなたが時々彼らに影響を返すことができることを意味します.
“カードを扱う”のためのマップされた変換を持っていると言う.ゲームのルールについてもっと知っているいくつかの他のアクションは、この変換を呼び出します.しかし、カードのドメインロジック(cards.jsの秘密)は、デッキが使用されるとき、新しいデッキがシャッフルされて、扱われる必要があると言います.次のようになります.
//this is cards.js

//...

const wire = ({getter, setter, ...other}) => {

    //...

    const _deal = (state) => {
         if (!cardsRemaining(getter(state))) {
             return [state, shuffle(DECK, DealNewDeck)
         } else {
             return setter(state, deal(getter(state)))
         }
    }

    return {
        deal: _deal
        //...
    }
}
//...
どちらのアクションがdealと呼ぶのであれば、新しい状態ではなく状態効果タプルを返すこともある.このアクションは、dealが返された効果を含む状態効果タプルを返すようにする必要があります.
これにより、実装するのはかなり面倒です.さらに、モジュールの分離は、これが他のモジュールについて考える必要がなかったなら、よりよいでしょう.
たいていの場合、この状況をデザインで避けることができます.まず試しなさい.さもなければ、何も完全でありえないことを受け入れて、モジュールの純度を壊して、平和にしてください.

一方、ダークサイドで.
それとも?さて、アクションからそれらを返すことなく効果を実行することができますハック(強いハッキング)があります.私はそれを推薦しません、しかし、それは少なくとも知っているためにおもしろいかもしれません.
上の例を挙げて、タプルを返すdealの代わりに、次のように実装します.
const _deal = (state) => setter(state, 
    cardsRemaining(getter(state))
    ? deal(getter(state))
    : {...getter(state), needsNewDeck: true}
)
また、新しいデッキを必要とするときに何をすべきかを操作する必要があります.
const GetNewDeck = (state) => [
    setter(state, {...getter(state), needsNewDeck: false}),
    shuffle(DECK, DealNewDeck)
]
どのように我々はもはやマップ化された変換からの影響を返すかを参照してください?その代わりに、我々は派遣されることになっている適当な行動にそれを動かしました.しかし、どのようにそれを派遣するか?それがハックが来るところです.
パラメータをモデルとするカスタムサブスクリプション関数を作成できます.これは、サブスクリプション機能を取得するたびに、モデルの変更を実行します.そこからGetNewDeckを派遣できます.
const mySub = (dispatch, model) => {
    requestAnimationFrame(() => {
        model.needsNewDeck && dispatch(model.GetNewDeck)
    })
    return () => {} //noop
}

const subs = model => [
    [mySub, model],
    //...other, real subscriptions I might need
]
状態変更のたびに実行されるサブスクリプション関数は、サブスクリプションがどのように使用されるべきではないかということです.requestAnimationFrameは、ハイパーアプリスケジュールがどのように内部的に購読更新を行うかの実装詳細についてのみ動作することです.
したがって、実際にアクションからそれらを返すことなく効果を実行する必要がある場合、それはあなたがそれを行うことができる方法です.HyperAppは意図的にこの使用をサポートしていないことに注意してください.

ほとんど完了
ビューコンポーネントを使用してビューを分割して克服する方法について議論しました.また、原始的なトランスフォームを使用したビジネスロジックと、ゲッター、セッター、およびマップ付きの変換を使用したすべての配線についても説明しました.最終的な部分といくつかの役に立つ閉鎖の言葉は、パート7であなたを待ちます.