条件分岐とリスト
Show で所有スコープを持つ条件分岐を作り、For でキー付きリストをトランザクションとして更新します。
Show による条件分岐
Show は読み取り専用の真偽値 signal から分岐を選びます。children と省略可能な fallback はファクトリで、選ばれた分岐は新しい子スコープの下で作られます。
import { Show, signal } from "@takazudo/zfb/zudo-react";
function Details() {
const expanded = signal(false);
return (
<section>
<button on:click={() => { expanded.value = !expanded.value; }}>
Toggle details
</button>
<Show
when={expanded}
fallback={() => <p>More details are available.</p>}
>
{() => <p>The expanded content is mounted here.</p>}
</Show>
</section>
);
}選ばれたファクトリは、その分岐が生きている間に一度実行されます。別の分岐に切り替えるとスコープを一度破棄して領域を削除し、戻すと新しい分岐インスタンスを作ります。false で fallback がなければ子スコープは作りません。リアクティブな hidden 属性は既存の DOM を隠すだけで、分岐やそのリソースは破棄しません。
For によるキー付き項目
For は読み取り専用の配列 signal を読み、各通常の項目に by を呼んで文字列または有限数のキーを得ます。
import { For, signal } from "@takazudo/zfb/zudo-react";
function TodoList() {
const todos = signal([
{ id: "a", title: "Read the guide" },
{ id: "b", title: "Ship the feature" }
]);
return (
<ul>
<For each={todos} by={(todo) => todo.id}>
{(todo, index) => (
<li>{index}: {todo.value.title}</li>
)}
</For>
</ul>
);
}item と index の引数は読み取り専用の signal です。JSX に渡すと更新されるバインディングになり、value を読むとスナップショットになります。維持されたキーでは、領域、子スコープ、ref、イベントハンドラー、コントロールの状態、item/index の signal オブジェクトが保たれます。同じキーの別オブジェクトに置き換えると読み取り専用のビューは更新されますが、ファクトリは再実行されません。
キーは文字列か有限数です。負のゼロはゼロに正規化されますが、数値の 1 と文字列の "1" は別のキーです。For は DOM やスコープの状態を変える前に次のキー全体を検証します。重複キーは ZR_DUPLICATE_KEY、未対応のキーは ZR_KEY_TYPE を報告し、元のリストはそのままです。初期キーの重複はサーバーレンダリングを失敗させ、ハイドレーション中の不正なキーはその島を失敗させます。
古い領域を置き換える前に、新しい項目を仮に構築します。セットアップに失敗したら仮のスコープを破棄し、元のリストを有効なまま残します。キーを削除するとそのスコープを一度だけ破棄し、後で同じキーを追加すると新しい項目を作ります。維持されたキーを移動する場合は既存の DOM を移動し、可能な限りフォーカスと選択範囲を保ちます。
動的なテーブル行
For は tbody の内部には置けません。コメントで区切られる領域は、そのテーブル構造上の位置では有効ではありません。行をまとめて入れ替えるには、1 要素だけを持つ For をテーブルの外側に置き、item のスナップショットから直接 tr 要素を作ります。
import { computed, For, signal } from "@takazudo/zfb/zudo-react";
function ResultsTable() {
const data = signal({
rev: 0,
rows: [{ id: "a", name: "First" }]
});
const table = computed(() => {
const { rev, rows } = data.value;
return [{ rev, rows }];
});
// To replace the rows:
// data.value = { rev: data.value.rev + 1, rows: nextRows };
return (
<section>
<For each={table} by={(item) => item.rev}>
{(item) => {
const { rows } = item.value;
return (
<table>
<tbody>
{rows.map((row) => <tr><td>{row.name}</td><td><input aria-label={row.name} /></td></tr>)}
</tbody>
</table>
);
}}
</For>
</section>
);
}revision が変わるたびに古いテーブルとそのスコープを破棄し、新しいテーブルを作ります。古いテーブル内のフォーカス、選択範囲、スクロール位置、フォームコントロールの状態は失われます。セルの値だけが変わる場合は、テーブルを入れ替えずにセルへ signal のライブバインディングを使えます。
パーサー上の位置と変換中の入力
For と Show の領域は、コメントで区切った範囲が有効な位置に置く必要があります。table の構造上の階層や select の選択肢の内部では使えません。特殊な HTML の中に動的領域を置く前に、パーサーの文脈に関する規則を確認してください。
フォーカス中の項目がテキストを変換しているとき、その項目を切り離す並べ替えは変換終了または blur まで待ってから最新のリストを適用します。その項目を削除する操作は明示的な unmount であり、リソースを取り消します。