皆さんは、CSSが得意ですか? 私は大の苦手で、ずっと避け続けています。しかし、何度も何度も向きあおうとしていました。もし共感していただける方には朗報です。
と言う懇切丁寧で、PRE_CSSと言うCSSモジュール設計(OOCSSやBEMなどの仲間です)の設計者本人が書かれた書籍になります。まだ読み初めではありますが、とても感銘を受けた書籍のためレビューの代わりに、Elmでとにかく実践してみると言う形で恩返しをさせていただきます。興味が湧かれた方は是非、購入を検討してみてください。
今回の記事は、肩慣らしということでエレメントモジュール(3.2参照)のボタンに焦点を当ててみました。コードは、こちら
まずは、愚直に実装してみる
今回対象となるボタンは「標準ボタン」「標準ボタンのDisabled状態」「前にアイコンがあるボタン」の三つになります。今回はViewだけに焦点を当てるため、Modelはどのボタンが押されたかの状態を表すもの。Msgもそれに合わせたものになります。
module Main exposing (main)
import Browser
import Html exposing (Html, a, article, div, p, text)
import Html.Attributes exposing (class, href)
import Html.Events exposing (onClick)
-- MAIN
main : Program () Model Msg
main =
Browser.element
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
-- MODEL
type ClickState
= Standard
| Downloading
| UnClick
type alias Model =
ClickState
init : () -> ( Model, Cmd Msg )
init _ =
( UnClick, Cmd.none )
-- UPDATE
type Msg
= StandardClick
| DownloadClick
update : Msg -> Model -> ( Model, Cmd Msg )
update msg _ =
case msg of
StandardClick ->
( Standard, Cmd.none )
DownloadClick ->
( Downloading, Cmd.none )
-- VIEW
view : Model -> Html Msg
view model =
article [ class "ly_cont" ]
[ div [ class "bl_buttonSimulation" ]
[ a [ class "el_btn", onClick StandardClick, href "#" ] [ text "標準ボタン" ]
, a [ class "el_btn el_btn__disabled", href "#" ] [ text "標準ボタン" ]
, a [ class "el_beforeIconBtn el_beforeIconBtn__download", onClick DownloadClick, href "#" ] [ text "ダウンロード" ]
, p [ class "bl_buttonSimulation_clickState" ]
[ text <|
case model of
Standard ->
"標準ボタンがクリックされました"
Downloading ->
"ダウンロードボタンがクリックされました"
UnClick ->
""
]
]
]
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
エレメントモジュールのように、どのページ・レイアウトでも使われる可能性があるため、上記のコードのように剥き出しのHTMLタグのままコピー&ペーストを繰り返して開発・運用してしまうと変更に弱く・抽象度が低いためコードの見通しも悪くなってしまいます。
エレメントモジュールを疎結合にする
ElmはSPA時のページ単位ぐらいの粒度以外でのコンポーネント指向は現在推奨されていません(推奨されている時代もありましたが)。しかし、常にコードを具体的に書くという意味ではなく、むしろエレメントモジュールのようなHtml msg
については積極的にモジュール分割や関数による抽象化を推し進めるべきでしょう。今回は、アイコンとボタンにフォーカスを当てて分割と抽象化を行いました。
一つのアプリケーションにおけるアイコンの使用量は基本的に現実的な数で納まり、使用箇所や変更が簡単に行えると実に堅牢です。愚直に書くケースではclass文字列でコードが散りばめられる可能性がありましたが、カスタムタイプとモディファイア(3.3参照)への変換関数を用いて使用することで安全に使用することができます。
module Element.Icon exposing (Icon(..), toButtonModifier)
type Icon
= Download
toButtonModifier : Icon -> String
toButtonModifier icon =
case icon of
Download ->
"download"
ボタン用のモジュールを作り、画面ごとのオレオレボタンが生成されるのを防ぎましょう。Disabled状態のボタンはMsgを発行される必要がないためプロパティとしてdisabledフラグを受け取ることで、onClickを持つボタンとそうでないボタンに分岐してカプセル化を行いました。beforeIconButtonは先ほど定義したIconを受け取り中で変換することで、確実に存在するアイコンだけを使うことができるようになりました。とても堅牢です。
module Element.Button exposing (beforeIconButton, standardButton)
import Element.Icon as Icon
import Html exposing (Html, a, text)
import Html.Attributes exposing (class, href)
import Html.Events exposing (onClick)
type alias ButtonProps msg =
{ text : String
, onClick : msg
, disabled : Bool
}
standardButton : ButtonProps msg -> Html msg
standardButton props =
if props.disabled then
a [ class "el_btn el_btn__disabled", href "#" ] [ text props.text ]
else
a [ class "el_btn", onClick props.onClick, href "#" ] [ text props.text ]
type alias IconButtonProps msg =
{ text : String
, onClick : msg
, icon : Icon.Icon
}
beforeIconButton : IconButtonProps msg -> Html msg
beforeIconButton props =
let
iconModifier =
"el_beforeIconBtn__" ++ Icon.toButtonModifier props.icon
in
a [ class <| "el_beforeIconBtn " ++ iconModifier, onClick props.onClick, href "#" ] [ text props.text ]
最後は、定義したボタンの関数を呼び出して使ってみましょう。このようにコンポーネント指向でなくても、見通しが高く・再利用性と拡張性に優れたモジュールを作ることができました。ここでのポイントはMsgやボタンの状態であるdisabledなどを閉じ込めずに、プロパティとして渡すことができ、それらは使用側でハンドリングすることです。
view : Model -> Html Msg
view model =
article [ class "ly_cont" ]
[ div [ class "bl_buttonSimulation" ]
[ standardButton { onClick = StandardClick, disabled = False, text = "標準ボタン" }
, standardButton { onClick = StandardClick, disabled = True, text = "標準ボタン" }
, beforeIconButton { onClick = DownloadClick, icon = Icon.Download, text = "ダウンロード" }
, p [ class "bl_buttonSimulation_clickState" ]
[ text <|
case model of
Standard ->
"標準ボタンがクリックされました"
Downloading ->
"ダウンロードボタンがクリックされました"
UnClick ->
""
]
]
]
まとめ
CSSが大の苦手な私ですが、Elmでさらに堅牢な高いコードにし、実践スピードの速さを上げ、最高の書籍を駆使することで克服の一歩を歩めています。ありがとうございます。CSSもElmもどちらもがんばっていきましょう。