コード例

Apple Store模写(複雑なステート)

Apple Storeの模写

ここではApple Storeを模写しながら、Turbo, Stimulusのステートの使い方を考えてみます。Apple Storeの場合は以下のようになっています

  • オプションを選択すると、それに応じてリアルタイムで表示価格が変更されます
    • 合計金額
    • 各オプションの隣に表示される合計価格 (例えば現在のモデルのRAMを1TBに変更した時に合計はいくらになるか)
  • カラーオプションを選択すると、それに応じて画像が切り替わります
  • 前のオプションを選択するまで、次のオプションは選択不可能状態です。オプションを選択すると、次のオプションは選択可能に切り替わります

また、オプション選択をしてもネットワーク通信は発生しません。価格更新を含めてすべてブラウザだけでやっています。

このページの複雑さの要因は下記のものと言えます。コードをちゃんと整理しないとスペゲッティーになる程度には複雑です。

  • 複数のアクションがステートを変更しています
  • ステートからページの複数の要素が同時に更新されています
  • 選択されたオプションから価格を計算する処理があります

複数の技術で実装して、比較する

比較的ステート管理が複雑な例ではないかと思いますので、これを使ってHotwireによるステート管理を議論したいと思います。以下の方法を検討します。

  • サーバでステート管理する場合: オプションを選択するたびにサーバと通信を行い、レスポンスとして更新された画面を受け取ります。サーバ通信が発生するにも関わらず、Turboで良好なUI/UXを保つ方法を確認します
  • ブラウザでStimulusを使ってステート管理する場合: 実際のApple Storeと同様にすべてブラウザで更新をします(価格等を含めて)。複雑なステートをどのようにStimulusで管理し、画面の複数箇所を効率的に(コードをスパゲッティ化にせずに)更新するかを確認します
  • Reactを使ってステート管理する場合: 実際のApple StoreはReactを使用しています(MPAに埋め込むタイプ)。ブラウザだけでステートを管理し、ネットワーク通信せずに画面を更新するのはReactの得意分野です。Hotwireを使った方法と比較し、何が利点なのかを確認します

実装してみた結果

実装した結果の詳細は関連ページでご確認いただけます。ここでは全体を通した印象をお伝えします。

  • 比較的複雑なステートであっても、Hotwireで問題なく対応できます
    • ステートをサーバに持たせた場合が一番簡単になります。Reactよりも簡単になります
    • サーバ通信をせずにStimulusだけでステート管理した場合でも、StimulusのValuesでスッキリ対応できます
  • 書きやすさの違いはReact vs Hotwireではなく、HTMLテンプレートシステムの利用による
    • ERBやJSXのようなHTMLテンプレートシステムを使い、HTMLとロジックを混ぜた場合はコードは書きやすいです(改めてPHPの偉大さを思い起こさせてくれます)
    • すでに用意されたHTMLに対して、後からStimulus targetを繋げて更新する場合は、ファイルを行き来しなければならないので多少書きにくくなります。ステートをすべてStimulusに持たせた場合がこのケースです
    • ただ、HTMLとロジックを分割するか否かだけの違いであり、大きな違いとは感じませんでした
  • いずれのケースでも、ビジネスロジックをなるべくマークアップから分離し、IPhone.jsIphone.rbなどのview modelに持たせることが効果的です。View Modelにしっかりロジックを収められれば、上記の3手法の違いは些細に感じられました

Stimulusで複雑なブラウザステートは管理できるか?

上記のことから、Stimulusでもある程度複雑なブラウザステートが管理できることがわかりました。そもそもステート管理はviewがやることではなく、modelなどで行うものですので、StimulusとReactで同様に管理できることは当然と言えば当然です

StimulusとReactのコードの主な違いは、modelのデータをviewに反映させるところです。ReactはJSXテンプレートをすべて再レンダリングしますので、データをviewに反映させる処理がわかりやすくなっています。さらにこれを自動的にやってくれます。Stimulusの場合はここが多少煩雑になります。

一方で、ステートをサーバ側で管理する場合が一番簡単です。ブラウザでステートを管理する場合はCatalogクラス(app/models/catalog.rb)で製品情報を一旦すべて集約して、これを一気にブラウザに送る必要があります。それに対してサーバ側で管理する場合はIphoneクラス(app/models/iphone.rb)でその都度計算するだけです。

複雑さはモデルで管理すること

上述のようにそもそも複雑なステートはmodelの問題です。StimulusやReactのようなview/controllerライブラリの問題ではありません。

一番の教訓はmodelの中でしっかり複雑さを管理することでしょう