はじめに
まず、この記事は、解決策ではないです。
私が課題に感じていることのメモと、できればだれか、いい方法があったら教えてほしいなぁという内容です。
課題
Reactで、親子関係のコンポーネントがあり、1組の親と子だけでなく結構深くて、子たちを包含するよう親を更新しないといけない状況です。
SVGで描いているので、親のサイズは、HTMLのように勝手に広がってくれたりせず、自分で再計算しないといけない。(ブラウザはどうやってるんだろう🤔 深さ優先の逐次処理か)
雑に描くと↓こんな感じ。
入れ子になっていて、子も、詳細を計算してみないとサイズが確定しないので計算してみた後に、子から親へ、「自分のサイズは100pxでしたー」「子の絵は描いておいたので親さんは親さんでよろしくー」と伝えていく。受け取った親は、子たちのサイズを合計したり子たちの隙間を考えながら、親自身のサイズを決定して自身を描く。そしてまたその親へ・・・。
として実装してみたところ、どうやら複数の子からの要求がほぼ同時に上がってきたとき、親も自分で計算していると、子の要求が一部無視されるような不整合が発生しました。
大げさに書くと、こんな状況だと思います。
結果的に、オレンジの310pxでは描画されず、水色の110pxになる。子1、子2は、子自身によって適切な位置に描画されている。なぜ水色がオレンジに追い抜かれるかは疑問ですが、微妙なタッチの差とか、子1が特殊だったり、いくつかの条件が重なって起きる模様。
詳しく説明はしませんが、意図的にこの問題を発生させてみたコードは下記。このサンプルでは、子たちの最大の高さを、親の高さとして包含する、というものです。幅ではなく。(本質的には、子の計算結果を使って親を計算するという意味で同じということで。)
親が灰色、子たちが緑で、数字は高さです。子はランダム(0~99)で自分の高さを決め、親は子たちの最大の高さを使って自分の高さを決めます。
一番右端の26
が親が認識した子たちの最大高さで、子たちを包含するはずなのに、最小の26
の子に合わせた高さになってしまっている。正解は当然97
。
親ではわざと、大きな子ほど速く、小さな子ほど長く待ってから親を計算することで、再現させています。上に描いた手書きの絵の"計算中・・・"のところ。
対応方法の検討
いくつかの方法があるんじゃないかと思いますが、描画処理の頻度を減らすという意味でも、子たちの要求をまとめて、親はまとめて描画する方法がベターなんじゃないかと思ってます。
JavaScriptでいうとPromise.all()
のイメージ。トリガーは子だけど、子を描画せよ!というのは親なので、いけるような気もする。それを、Reactでどう書くのかわからないけど。
あと、この手のことは、DB更新の世界では根幹の話で、方法が確立されています。更新する際にレコードをロックして、ほかのセッションの更新処理は待たせるという方法。
上の絵の例でいうと、水色が親での計算(更新)をするときに、親のレコードをロック(select for update)して、他の処理は待たせる。オレンジはスタートで待たされるので、追い抜かれることはない。
この手法は、DB更新の世界では悲観的ロックという名前がついています。この処理は作るのが大変なので、もし本当にやるなら、この問題が起きていると確信してからやりたい。極力やりたくない。たぶんやらない。やれない。(悲観🤣)
もう1つDB更新の世界で楽観的ロックというのがあって、これは更新する前にもう一度最新状態をチェックするという方法。
上の絵の例でいうと、水色の世界線で親を更新する直前に、最新の子1と子2をもう一度チェックすることによって、「自分の手持ちの子2は0pxだけど、DBでは200px・・・違うじゃん!あっぶね」と気づけるという技です。
DBの場合でいうと、違う場合はエラー処理として捨てるかもしれない。でも子2の正解がわかったんだから、もう一度最初から計算してもいいよねと思う。
これは、Reactでできるかなぁ。ちょっとわからない。水色の世界線からオレンジの世界線(JavaScriptではセッションと呼ぶのかわからないけど)の値をとるみたいなものだから。でももしそれができるなら、仕組みとしてはそんなに難しくなさそう。
ちなみに悲観的ロックは多くの短時間の処理が起こるときに使うべしとされ、楽観的ロックは処理があまり多くないときとされている。その意味では、どちらかというと、悲観的ロックが必要な場面な気はしている。
さらに別の方法の案。useContext
を使って、1か所で更新すればという話も、雑に調べると出てくる。でもそれでは、この問題は解決しなさそう。今、親でわざと「小さな子ほど長く待つ」という処理をやっているけど、それがcontextの更新で発生したら同じだから。
おわりに(課題のメモとしては終わり)
どうするかはまだ決めていませんが、課題と思いついた解決案は書いておくという、githubにissueを登録したようなエントリーでした。
Reactの中の処理が結構頑張ってくれているはずなんだけど、こういう問題が起きてしまうのは、たぶん私のコーディング方法がよくないんだと思う。そのために、力技で専用の回避ロジックを入れることが正しいのかという疑問にぶち当たる。そもそも正しいコーディングをすることが、一番の回避方法なんだろう。
でもそういうことは、「はじめてのReact」みたいな本にはあまり書いてなくて、プロの現場の間で受け継がれる技なんだろうと思う。
Reactでもなんでも、独学での技術習得の道の険しさは、こういうところだと思った。