前書き
purescript-affjax
はajaxする際のデファクトスタンダードと言って良いと思います。
通信が成功したか失敗したかはEither
で受け取ることができますが、実際にはEither
やMaybe
を直に取り扱うのは辛いです。
動機付けとして、Halogenのexamplesにaffjaxを使う例( effects-aff-ajax )があるので見てみます。以下のようにMaybe
でモデル化されています。
-- PureScript
type State =
{ loading :: Boolean
, result :: Maybe String
}
HalogenとAffjaxを連携させる例としては十分ですが、実際には扱いづらいです。
RemoteData
Elmのチュートリアル( HTTP )では以下のようにモデルを設計しています。
-- Elm
type Model
= Failure
| Loading
| Success String
Maybe
と上記モデルを改めて比べてみると、前者はFailure
とLoading
という区別されるべき2つの状態を全てNothing
として忘却してしまうので扱いづらくなってしまっていることがよく分かります。
Maybe
を利用した設計の問題点は How Elm Slays a UI Antipattern でも説明されています。
そして、同記事で紹介されているのがRemoteData
と名付けられたカスタム型です。
-- Elm
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
使ってみる
PureScriptへの移植版であるpurescript-remotedata
パッケージがあるのでそれを使います。
もし独自で実装する際は、Elmとは異なりdata
を使います。
--purescript
data RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
以下は良く使いそうな関数の例です。
fromEither :: forall e a. Either e a -> RemoteData e a
isLoading :: forall e a. RemoteData e a -> Boolean
今回は以下のようにインポートして使います。
import Network.RemoteData (RemoteData(..))
import Network.RemoteData (fromEither, isLoading) as RemoteData
RemoteData
を使うと冒頭のモデルは以下のように書けます。
-- PureScript
type State = { request :: RemoteData AX.ResponseFormatError String }
実際にアクションと結びつけると以下のようになるかと思います。
handleAction ActionName = do
response <- H.liftAff $ AX.get AX.ResponseFormat.string ("URL")
H.modify_ $ _ { request = RemoteData.fromEither response.body }
Submitボタンを作る
submitButton
:: forall e a m
. RemoteData e a
-> String
-> H.ComponentHTML Action () m
submitButton remote text = HH.button
[ HP.disabled $ RemoteData.isLoading remote
, HP.type_ HP.ButtonSubmit
]
[ HH.text text ]
ロード中はdisabledをtrueにするという処理が簡単に書けます。
UIのパターンマッチング
view :: forall e m. RemoteData e String -> H.ComponentHTML Action () m
view remote = HH.div_
case remote of
NotAsked ->
[ HH.p_ [HH.text "Enter username and click button"]]
Loading ->
[ HH.p_ [HH.text "Working..."] ]
Failure _ ->
[ HH.p_ [HH.text "Failed for some reasons. Please try again!"]]
Success res ->
[ HH.h2_
[ HH.text "Response:" ]
, HH.pre_
[ HH.code_ [ HH.text res ] ]
]
パターンマッチングでコードが読みやすいです。
結論
AffjaxとRemoteDataはセットで使おう!