Upgrade to Pro — share decks privately, control downloads, hide ads and more …

useSyncExternalStoreを使いまくる

 useSyncExternalStoreを使いまくる

TOMIKAWA Sotaro

December 10, 2024
Tweet

More Decks by TOMIKAWA Sotaro

Other Decks in Programming

Transcript

  1. © ZOZO, Inc. https://wear.jp/ 3 • あなたの「似合う」が探せるファッションコーディネートアプリ • 1,700万ダウンロード突破、コーディネート投稿総数は1,400万 件以上(2024年9月末時点)

    • コーディネートや最新トレンド、メイクなど豊富なファッション 情報をチェック • AIを活用したファッションジャンル診断や、フルメイクをARで試 せる「WEARお試しメイク」を提供 • コーディネート着用アイテムを公式サイトで購入可能 • WEAR公認の人気ユーザーをWEARISTAと認定。モデル・タレン ト・デザイナー・インフルエンサーといった各界著名人も参加
  2. © ZOZO, Inc. 4 WEAR Webの今と役割 WEAR (wear.jp) は絶賛リプレイス中! VBScriptという古の言語で一部稼働しており、

    Next.js(Pages Router) / React環境に移行中。 服でお困りのユーザーにGoogleなどの検索で辿り着いてもらい、 多くのユーザーにファッションの参考にしてもらうのが使命。 リプレイスだけではなく日々改善を積み重ねていく必要がある... 今日はそんな前提の話。
  3. © ZOZO, Inc. 5 改善のために ひと口に改善といっても守らなければいけないラインがある。 • リプレイス前環境の制約 ◦ 認証・認可

    • リプレイスに基づく制約 ◦ パスの最適化 • 各種キャッシュ • etc... 他所への影響を最小限に抑えながら素早く改善したい。 できればABテストで影響も見ながら。
  4. © ZOZO, Inc. 7 middleware export const middleware: NextMiddleware =

    (req) => { const res = NextResponse.next(); res.cookies.set( COOKIE_NAME, random(PATTERN_A, PATTERN_B), ); return res; }; ヨシ。
  5. © ZOZO, Inc. 8 コンポーネント export const Component: React.FC =

    () => { const pattern = getCookie(COOKIE_NAME); if (pattern === PATTERN_A) return ...; // PATTERN_B return ...; }; SSRできない。(getCookieはクライアントサイドでのみ使えるものとする)
  6. © ZOZO, Inc. 9 SSR対応 export const Component: React.FC =

    () => { const pattern = typeof window !== undefined ? getCookie(COOKIE_NAME) : PATTERN_B; if (pattern === PATTERN_A) return ...; // PATTERN_B return ...; }; SSRするとHydration Errorが発生する。
  7. © ZOZO, Inc. 10 useEffectでマウント後に取得 export const Component: React.FC =

    () => { const [pattern, setPattern] = useState(PATTERN_B); useEffect(() => { setPattern(getCookie(COOKIE_NAME)); }, []); if (pattern === PATTERN_A) return ...; // PATTERN_B return ...; }; クライアントサイドレンダリングでも無駄な再描画が発生する。
  8. © ZOZO, Inc. export const Component: React.FC = () =>

    { const pattern = useSyncExternalStore( () => () => {}, () => getCookie(COOKIE_NAME), () => PATTERN_B, ); if (pattern === PATTERN_A) return ...; // PATTERN_B return ...; }; 11 useSyncExternalStore
  9. © ZOZO, Inc. 13 useSyncExternalStoreとは 基本は「第2引数の返り値を返す」フック。 const value = useSyncExternalStore(

    () => () => {}, () => "#zup_frontend", ); return value; // "#zup_frontend" ... なにがうれしいんだっけ?
  10. © ZOZO, Inc. 14 唯一無二 第三引数 SSR時とHydration時は、第2引数の代わりに第3引数に渡した関数が実行される const value =

    useSyncExternalStore( () => () => {}, () => "Now CSR (window/document are available)", () => "Now SSR or Hydrating", ); ブラウザ側でしか使えない関数やリソースを必要な時だけ利用できる。 Hydration Errorの回避にも使える。
  11. © ZOZO, Inc. 15 () => () => {} 第1引数のコレ↑はなに?

    外部ストアへのサブスクライブ
 をするための関数。 サブスクライブ(=購読≃同期)をする必要がないときは () => () => {} この形になる。
  12. © ZOZO, Inc. 16 第1引数 第1引数をもう少し細かく確認する。リアルな例を見てみる。 createdAtをHydrationに配慮しながらフォーマットする例: const formatted =

    useSyncExternalStore( (callback) => { const id = setInterval(callback, 60 * 1000); return () => clearInterval(id); }, () => formatTimeDelta(new Date(createdAt)); () => new Date(createdAt).toISOString(); );
  13. © ZOZO, Inc. 17 第1引数 Reactの外にある何か(window,document,store,etc...)と同期したいときに利用。 const subscribe = (callback)

    => { // 値などが変化したらcallbackを呼ぶ const unsubscribe = () => { // コンポーネントが破棄されるときに呼ばれる }; return unsubscribe; }; callbackが呼ばれると、第二引数に渡した関数が再評価されて値が変化する。
  14. © ZOZO, Inc. 18 使い方いろいろ • Hydration Error回避 • mediaQuery

    (window.matchMedia) • ResizeObserver • localStorage • navigator.onLine • フレームワークを跨いだsignal/store同期 • 独自store実装 • navigator.canShare • クライアント限定fetch処理 • etc...