結論
実装に無理があるので、OpalからReactを使いたい。
これ何
Ovtoはhyperappをベースにしている。Ovtoではstateはappに対して1つのツリーしかない。 つまり、各コンポーネントに閉じたstateを持つことはできず、全てはグローバルなstateを触ることになる。
これが嫌になったので、コンポーネントに閉じたStateを作ってみた。
実装
module Ovto class LocalStateMiddleware < Ovto::Middleware('local_state') class State < LocalStateMiddleware::State item :generation, default: 0 end class Actions < LocalStateMiddleware::Actions def redraw { generation: state.generation + 1 } end end end module LocalState def local_state(&default) @local_state ||= default.call end def set_local_state(state) @local_state = @local_state.merge state actions.local_state.redraw end end Ovto::Component.prepend LocalState end
使い方
まずAppでLocalStateMiddlewareをuseする。
class MainApp < Ovto::App use Ovto::LocalStateMiddleware # ... end
そうするとstateを使いたいコンポーネントでlocal_state
とset_local_state
メソッドが使えるようになるので、それを使ってstateの取得/更新をする。
コードは適当なので動かないかもしれない。
class C < Ovto::Component class State < Ovto::State item :counter, default: 0 end def render s = local_state do # デフォルト値 State.new end o 'div' do o 'span', s.counter o 'button', { onclick: -> (_) { set_local_state(counter: s.counter + 1) } } end end end
実装の解説
OvtoではOvto::Component
のインスタンスの寿命はだいたいrenderされているときと一致するので、インスタンス変数に適当にstateを突っ込みつつ、renderし直すためにMiddlewareのstateを更新している。
既知の問題
Reactでkeyが必要になるようなケースで、コンポーネントがrenderされる順序が変わるとOvto::Component
のインスタンスが作り直されて、stateがリセットされるような気がする。
試していないので未確認。