TT Lab
はじめる
学ぶ 学習パス コース

SSR — サーバが先に描く

ハイドレーション比較器

TT Labで続きを見る

目標

サーバーが描いたツリーとクライアントが描いたツリーを突き合わせて食い違った場所を見つけ出し、それをどの境界まで描き直す必要があるかに変換する比較関数を作ります。

なぜ重要なのか

ハイドレーションはすでにあるDOMにハンドラーを取り付ける作業なので、両側が違うものを描くと、画面は静かに間違ったまま残ります。そのためフレームワークは、食い違った場所を見つけて知らせることに力を入れていますが、その通知が役に立つには2つのことが必要です。

1つ目は、形が食い違った場所で止まることです。一覧の先頭に項目が1つ割り込むとその下がまるごとずれますが、そのまま下りていくと不一致が何百件も出てきて、本当の原因が埋もれます。2つ目は、境界を知ることです。同じ不一致でも、境界の内側ならその配下だけ描き直せば済みますが、境界の外ならページ全体を描き直します。

ツリーとパスの形

ノードは次の2つのどちらかです。

パスは、ルートが"$"で、i番目の子は親のパス+"/"+iです。例: ルートの2つ目の子の、最初の子は"$/1/0"です。

不一致1件は{ path, kind }で、kindはtext・attr・tag・childrenのいずれかです。attrの場合は、nameに属性名をさらに入れます。

ステップ

  1. テキストの比較とパス
  2. 属性の比較: 順序は不一致ではない
  3. 形が食い違ったら止まる
  4. 抑制は1階層だけ
  5. 最も近い境界を付ける
  6. 描き直す場所の計算: repairPlan
  7. 実際に測って書く: /root/work/hydrate/07-report.txt
  8. 整理: /root/work/hydrate/08-notes.md

参考

テキストの比較とパス

/root/work/hydrate/hydrate.mjsでdiff(server, client)をexportしてください。食い違った場所ごとに{path, kind}を入れた配列を返します。テキストが違えばkindは"text"、片方だけがテキストなら"tag"です。同じツリーなら空の配列です。

mkdir -p /root/work/hydrate。パスはルートが"$"で、i番目の子は親のパスに"/" + iを付けたものです。再帰で下りながら、パスを引数として持ち回ってください。このステップでは、2つのツリーの形は同じとみなして構いません。

属性の比較: 順序は不一致ではない

要素ノードのpropsを比較してください。値が違う、または片方にしかなければ{path, kind: "attr", name}を入れます。属性の順序は不一致ではありません。

両側のキーを集めて1つの集合にしてから、名前ごとに値を比べると、順序が自然に抜け落ちます。片方にしかない属性は、反対側の値がundefinedなので自動的に引っかかります。ブラウザーは属性を順序で区別しないため、順序を不一致として数えると、問題のないページまで真っ赤になります。

形が食い違ったら止まる

タグが違えば{path, kind: "tag"}を1件入れて、その下には下りないでください。子の数が違えば{path, kind: "children"}を1件入れて、同じく止まります。

一覧の先頭に項目が1つ割り込むと、その下がすべてずれます。そのまま下りていくと、対応しないノード同士を比べることになって不一致が何百件も出てきて、本当の原因1つがその山に埋もれます。採点ツールが、1つずれた一覧でその点を確認します。

抑制は1階層だけ

diff(server, client, options)のoptions.suppressに、パスの配列を受け取ってください。そのパス自体のtextとattrの不一致だけを除き、その下のパスはそのまま報告します。tagとchildrenは抑制の対象ではありません。

3つ目の引数がなくても、空のオブジェクトでも、そのまま動作する必要があります。1階層だけ許可する理由は、ReactのsuppressHydrationWarningと同じです。時刻1か所を見逃すためにその下まで目をつぶると、その中の本当の不一致が永遠に見えなくなります。

最も近い境界を付ける

不一致ごとにboundaryを追加で入れてください。サーバーツリーでそのパスを囲む、最も近いboundaryの値で、囲むものがなければnullです。ノード自身が境界を開く場合は、そのノードの不一致も自分の境界に属します。

ツリーを1回たどりながら、パスごとに「今開いている境界」を記録しておけば、あとは調べるだけです。入れ子になった境界では、内側が優先されます。nullが出るかどうかが、そのまま設計の成績表です。その1つがページ全体を描き直させます。

描き直す場所の計算

repairPlan(mismatches)をexportしてください。{boundaries, full}を返します。boundariesは描き直す必要がある境界idを重複なく、最初に現れた順に入れた配列で、fullは境界の外の不一致が1つでもあれば真です。

同じ境界の中で不一致が10件出ても、描き直しは1回です。そのため答えは一覧ではなく集合ですが、人が読む順序は守っておくほうがよいです。境界の外の不一致はboundariesに入れず、fullにだけ示してください。

実際に測って書く

/opt/fixtures/ssr-hydrate/pair.jsonのserverとclientを、自分のdiffとrepairPlanに渡し、出てきた値を/root/work/hydrate/07-report.txtに이름=값の形式で3行書いてください(プレースホルダーは名前と値です)。mismatchesは不一致の数です。boundariesは描き直す境界の数です。fullは、境界の外の不一致があればyes、なければnoです。

短い.mjsを1つ作って実行し、出てきた値を書き写せば済みます。採点ツールが同じファイルを自分の比較関数でもう一度突き合わせて、書かれた値と照合するので、目で見当をつけないでください。子の数が違う場所で止まらないと、不一致の数が増えます。

何が不一致を閉じ込めたのか

/root/work/hydrate/08-notes.mdに3行以上で、サーバーとクライアントが違うものを描いたとき画面に何が見えるか、境界の外の不一致が何を意味するか、抑制を1階層だけ許可する理由は何かを書いてください。

本文に불일치、경계、억제が入っている必要があります(それぞれ韓国語で「不一致」「境界」「抑制」を意味する語です)。この3つが、SSRを使うチームが実際に何日もかけて向き合う3つのことです。