🧊

Qwikでinteractiveなコンポーネントをネストしたときの挙動

Partial Hydrationをネストできる、みたいなイメージ。
(正確には、QwikのコンテキストでいわゆるHydrationと呼ばれる処理は行われないけど)

素直にQwik-wayなコードを書くだけで、Resumableなコンポーネントがネストできるというだけ。

コード

import { useStore, component$, Slot } from "@builder.io/qwik";

export const Tab = component$<{ triggers: string[] }>(({ triggers }) => {
  const state = useStore({ tab: triggers[0] });

  return (
    <div>
      <div>
        {triggers.map((id) => (
          <button disabled={state.tab === id} onClick$={() => (state.tab = id)}>
            TAB: {id}
          </button>
        ))}
      </div>
      <div>{triggers.map((id) => state.tab === id && <Slot name={id} />)}</div>
    </div>
  );
});

export const Counter = component$<{ initialValue: number }>(
  ({ initialValue }) => {
    const state = useStore({ value: initialValue });

    return (
      <>
        <div>Value is: {state.value}</div>
        <button onClick$={() => state.value++}>+1</button>
      </>
    );
  }
);

export const App = component$(() => (
  <>
    <h1>Qwik is working!</h1>

    <Tab triggers={["A", "B"]}>
      <div q:slot="A">
        <h2>A</h2>
        <p>Hello~~</p>
      </div>
      <div q:slot="B">
        <h2>B</h2>
        <Counter initialValue={10} />
      </div>
    </Tab>

    <hr />
    <p>
      Made with ❤️ by{" "}
      <a target="_blank" href="https://www.builder.io/">
        Builder.io
      </a>
    </p>
  </>
));

これだけで、`Tab`が動作するようになった段階では、`Counter`がまだ動作しないようにできる。すごい。

コンポーネントの階層構造

- App
  - h1
  - Tab(interactive)
    - div(slot: A)
      - h2
      - p
    - div(slot: B)
      - h2
      - Counter(interactive)
  - hr
  - p

Astroなど、既存のPartial Hydration系のフレームワークだと、より上層のノードで1度しかその指定ができないので、`Tab`が動作するようになったら、`Counter`も動作するようになっちゃう(はず)。(手元でAstroで似たようなコードを試してみたら、そもそも`slot`内で`client:visible`とか書けなそうな挙動だった)