レンダリング

レンダリングとは、アプリケーションの状態とコンポーネントテンプレートの変更に基づいてDOMを更新するプロセスです。

Qwikは、非同期で、かつきめ細かくテンプレートをアウトオブオーダーでレンダリングする方法を知っている点でユニークです。

JSX

Reactと同様に、Qwikはコンポーネントのテンプレートを表現するためにJSXを使用します。JSXは単なる構文であり、内部的にはReactとQwikは完全に異なることに注意してください。JSX != VDOMです。

Qwikは、他のJSXフレームワークとはいくつかの違いがあります。

  • コンポーネントは常にcomponent$関数で宣言されます。
  • コンポーネントは、useSignalフックを使用してリアクティブな状態を作成できます。
  • イベントハンドラーは$サフィックスで宣言されます。
  • <input>の場合、QwikではonChangeイベントはonInput$と呼ばれます。
  • HTML属性が優先されます。classNameではなくclasshtmlForではなくfor
import { component$, useSignal } from '@builder.io/qwik';
 
export const MyComponent = component$((props) => {
  const count = useSignal(0);
  return (
    <>
      <button
        onClick$={() => {
          count.value = count.value + props.step;
        }}
      >
        Increment by {props.step}
      </button>
      <main
        class={{
          even: count.value % 2 === 0, // conditional class
          odd: count.value % 2 === 1,
        }}
      >
        <h1>Count: {count.value}</h1>
      </main>
    </>
  );
});

子コンポーネントのレンダリング

Qwikは必要に応じてコンポーネントを遅延ロードします。ダウンロードするコンポーネントの数を最小限に抑えるために、Qwikはコンポーネントのpropsが変更された場合にのみ子コンポーネントを調べます。

import { component$, useSignal } from '@builder.io/qwik';
 
export const Parent = component$(() => {
  const count = useSignal(0);
 
  return (
    <>
      <button onClick$={() => (count.value += 1)}>Increment</button>
      <Child name={'World_' + count.value} />
    </>
  );
});
 
export const Child = component$((props: { name: string }) => {
  return <p>Hello {props.name}</p>;
});

上記の例では、Parentコンポーネントは変更されるnameプロパティをChildコンポーネントに渡しています。Childコンポーネントは、countシグナルが変更された場合にのみ再レンダリングされます。

項目のリストのレンダリング

多くの場合、items.map()を使用してレンダリング関数で配列をマッピングすることにより、コンポーネントをレンダリングする必要があります。リストのすべての項目に、マッピング関数の最初の子に渡される一意のkeyプロパティが必要です。keyは文字列または数値で、リスト内で一意である必要があります。

import { component$ } from '@builder.io/qwik';
 
export const Parent = component$(() => {
  return (
    <>
      {data.map(({ message, uniqueKey }) => (
        <div key={uniqueKey}>
          <p>{message}</p>
        </div>
      ))}
    </>
  );
});

注: 特定のキーのデータが常に同じであることを保証できる場合を除き、配列のインデックスをキーとして使用することは推奨されません。キーとしてデータから一意の識別子を使用することが常に推奨されます。

条件付きレンダリング

条件付きレンダリングは、Javasciptの三項演算子?&&演算子、またはifステートメントを使用するだけで行われます。

import { component$ } from '@builder.io/qwik';
 
export default component$(() => {
  const show = useSignal(false);
  return (
    <>
      <button onClick$={() => show.value = !show.value}>Toggle</button>
      {show.value ? <h1>Visible</h1> : <h1>Hidden</h1>}
      {show.value && <div>Only show when it's visible</div>}
    </>
  );
});

dangerouslySetInnerHTML

Qwikは、DOMでinnerHTMLを呼び出す代わりに、dangerouslySetInnerHTMLというHTML要素の属性を提供します。

信頼できないコンテンツをレンダリングする際のクロスサイトスクリプティング(XSS)の可能性があるため、この操作が危険な可能性があることを思い出させるために、dangerouslySetInnerHTMLを使用する必要があります。

const htmlString = "<strong>hello</strong>";
<div dangerouslySetInnerHTML={htmlString}></div>

bind属性

bind属性は、<input>値をSignalに双方向データバインディングするための便利なAPIです。

チェックボックス入力の場合、bind:checkedを使用できます。これにより、checkedブール値が指定されたシグナルにバインドされます。

import { component$, useSignal } from '@builder.io/qwik';
 
export default component$(() => {
  const firstName = useSignal('');
  const acceptConditions = useSignal(false);
 
  return (
    <form>
      <input type="text" bind:value={firstName} />
      <input type="checkbox" bind:checked={acceptConditions} />
 
      <div>First name: {firstName.value}</div>
    </form>
  );
})

APIはシグナルを返さないため、useStoreでは機能しません。以下に示すように、valueとonInput$を組み合わせることで、手動アプローチを使用できます。

bind:は、Qwikオプティマイザーによってプロパティセットとイベントハンドラーにコンパイルされます。つまり、単なるシンタックスシュガーです。

import { component$, useSignal } from '@builder.io/qwik';
 
export default component$(() => {
  const firstName = useSignal('');
  const acceptConditions = useSignal(false);
 
  return (
    <form>
      <input type="text"
        value={firstName.value}
        onInput$={(_, el) => firstName.value = el.value }
      />
      <input type="checkbox"
        checked={acceptConditions.value}
        onChange$={(_, el) => acceptConditions.value = el.checked }
      />
      <div>First name: {firstName.value}</div>
    </form>
  );
})

このAPIは、変更元に関係なく、入力のvalueが常にシグナルの値と同期していることを保証します。

非同期レンダリング

レンダリングパイプラインが子コンポーネントを遅延ロードできることは重要です。遅延ロードは非同期操作であるため、レンダリングも非同期である必要があります。実際には、これはrender()関数がpromiseを返す必要があることを意味します。

現在のほとんどのフレームワークは、同期的なrender()プロセスを持っています。同期的なレンダリングは非同期のコード読み込みに容易に対応できないため、レンダリングを開始する前にすべての依存コンポーネントが事前に存在している必要があります。

レンダーバッチ処理

アプリケーションの状態が変化するたびに、Qwikはレンダリング処理をスケジュールします。レンダリング処理は、マクロタスク(例:setTimeout(0))の後で実行されるようにスケジュールされます。これにより、アプリケーションは複数の状態変更を単一のレンダリング処理にまとめて処理できます。

さらに、Qwikの非同期的な性質のため、すべてのDOM書き込みはバッファリングされ、すべてのコンポーネントがダウンロードされ、そのJSX関数が実行された後にのみフラッシュされます。その結果、UIはアトミックな操作として更新され、たとえアプリケーションのレンダリングが遅くても、ユーザーは中間段階を見ることはありません。

最終的な目標は、急速に変化する状態と遅いネットワークの状況下でも、パフォーマンスと一貫性のあるレンダリングを可能にすることです。

きめ細かいリアクティビティ

Qwikのきめ細かいリアクティビティのおかげで、状態に依存するコンポーネントのみが更新されます。これは、次の2つの理由から大きなパフォーマンス向上につながります。

  1. 実行するコードが少ないほど、アプリケーションの更新が高速にレンダリングされます。
  2. 実行するコードが少ないほど、多くの場合、アプリケーションの起動時(またはそれ以降)にコードをダウンロードする必要がなくなります。

コントリビューター

このドキュメントの改善にご協力いただいたすべてのコントリビューターに感謝します!

  • the-r3aper7
  • RATIU5
  • manucorporat
  • forresst
  • adamdbradley
  • zanettin
  • cunzaizhuyi
  • Pika-Pool
  • Kesmek
  • Craiqser
  • AnthonyPAlicea
  • mhevery
  • igorbabko
  • mrhoodz
  • thejackshelton
  • Balastrong
  • aendel
  • Jemsco