🍅

Svelte5の$derived挙動確認メモ

2024/09/05に公開


以前簡単に挙動確認していましたが本格的に使用するために$derived$derived.byについて色々挙動確認してみたところ、$effectに頼らずとも色々できるかもしれない?と思ったのでその確認メモです。$state変数の拡張のような使い方は1か月程度前は使えず萎えた気がするのですが、確認の仕方が悪かったのか、使えるようになった?かのようです。一部内容はSvelteの思想に沿わない使い方な気がするのでいつ動作しなくなるか不明です。
[svelte@5.0.0-next.243 (が最新リリースの状態でのREPLで確認)]

結論

  • $derived変数に直接手動で再代入はできない
    • $derived変数がオブジェクト,配列の場合、要素再代入,構造変更は可能
      • クラスは検証していない
  • $derivedの中で使用した$state変数の変更を検知すると更新される
    • 複数の$state変数が含まれているとそれら全てを検知する
    • 含まれる$state変数はオブジェクト,配列,クラスでも検知する
      • それらの 要素への個別代入,構造の変更,丸ごと再代入 いずれも検知する
    • $derivedの中で関数がネストしていても関係ない
      • ネスト含む全ての関数内で使用されている$state変数の変更を検知する
    • untrack内で使用した$state変数は変更検知の対象にならない
      • $effectと同じような検知仕様の模様

-----ここから危険思想-----

  • $derived内では他変数を変更できないが、$derived.by内では変更できる
    • $derived.by内の再代入されるだけの$state変数は変更検知の対象にならない
    • $derived.by内での変更は直感的な結果に反する場合がある
      • そのため$derived.byの実行タイミングを把握した上で実施する必要がある
    • $derived.byの実行タイミングは変更検知後$derived.by変数にアクセスしたタイミング
      • 遅延評価のようなイメージ
      • $derived.by変数にアクセスしなければ$derived.by内で他変数を更新できない

確認内容とコード

再代入不可の確認

  • $derived変数
    コンパイルエラーになる。

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn3O3QqCUAwH8FcZowtDoTyXfkHPkV3UccEh3ZFzZhDiu4eZaBHdbfv_2Nbj1dTkMTn2yOeGMMFD22KE8mjHxt-pFsIIve2cHieZ1860UpRcSk0C2nYsMeSw8XIWCvbb9CNSY1SRM3eqgjcOIX4pLkVb9gKGtaOGpkXBFvIC-nlzmEOcljKkP7j65mrNs93yK2eXTsQyWNa10be8X24OxZTF2W4qin9czVytOEbY2MpcDVWYiOtoOA1PjzyaMVwBAAA=

  • $derived.by変数
    コンパイルエラーになる。

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn2PzQrCMBCEXyUsHioWtTnWtuBzWA-arhBsNyXZFKTk3SXWf8Tb7s43M-wIJ92ig3w3Ah06hBy2fQ8p8KWPixuwZYQUnPFWxUvhlNU9VzXV3CILZTxxJkoxc3xgTNbzzYcko9Sg1QM2y-MlSeairMQYmZotsrf0yFiI7OYNtwiqWRlyLDQpix1OLXf701JGD4fND1x-4_IdL1avR6g4emZDwpBqtTqX46szVJOWFatpqP7h8oHLNxxS6EyjTxobyNl6DPtwBR2f0yh5AQAA

$state変数の拡張

オブジェクト

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACo1Q22rDMAz9FWH6kLDQNnlMk8C-Yx3UsZVhlljBkQtr8L-P1B1tR1f2pqNz0WUWnelxEuXbLKwcUJTidRxFJvhrXMB0xJ5RZGIi79TSqSblzMjN3u65RwZF3nIONawmlozJNt3dUcVCaXTmiDqZO6LyYsmgla6EbTgb7J4V2YmBPI-eoYYkhbqBpUk9rnv6SA6rOVpDeamKdUd0g1rp7tApHOI6nbeKDVkwVjkc0HKepDAv1DL4fMJLDfkuduISSfSGxwHFr4Dz9H-GSK0fuE_wt7naXP9uq9YzkwWyqjfqs56vV4Umcnm1iUXzTF78yItncql1aKTWNxqRiYG06QxqUbLzGN7DNzSri8dMAgAA

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = $derived({foo: count1, bar: 0});

  const output = () => console.log(`${count1}:${count2.foo}:${count2.bar}:${count2.baz}`);
  function increment1() {
    count1 += 1;
    output();
  }
  function increment2() {
    count2.bar += 1;
    output();
  }
  function add() {
    count2.baz = 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>
<button onclick={add}>add</button>

配列

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACo1QXWuDMBT9K5fQh8ikVR-tCvsdKtQlt1uYJmJuCkPy30eajbqylb3dc8_HPcnKzmpEy8p2ZXqYkJXseZ5ZyuhjDsBecCRkKbPGLSJsKisWNVPT6Y5GJBDGacqhhp2lgZBnyfEHVQRK4qIuKHkb1Slk_VWmOxJGWwLjaHYENfAE6gbC0oy4H80rP-3W6PLl11S0Wb8B-RYUvT_FBmenBSmjQWmx4ISacp7AGqhw9dr6qYb8GDexAY9e_3tAcRcQbv8zY5Dy3ryfnX3jefKHuTrcflpXL47IaDBajEq81-vtUb6JXF4d4tA8khff8uKRfJDSN4OUGw1L2WSkOiuUrKTFoe_9J487chU-AgAA

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = $derived([count1, 0]);

  const output = () => console.log(`${count1}:${count2[0]}:${count2[1]}:${count2[2]}`);
  function increment1() {
    count1 += 1;
    output();
  }
  function increment2() {
    count2[1] += 1;
    output();
  }
  function add() {
    count2.push(1);
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>
<button onclick={add}>add</button>

トリガー確認

基本

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACqVS227CMAz9FS_ioQgEa3krbad9xzoJSN0qWnCqxEVCVf59CqFcpu1pTz7xOfaxk4yiVRqdyD9GQfsjily8971YCj734eBOqBnFUjgzWBkyhZNW9VzVVLNGhoMxGkpgO-B2ykkzEKdQwszxnjF5nT9TWaAatOqETXJp8HatWaSQTzD7UbV5qFodzkkyh7KCMWhqVi1cOs2nRM0WebA0DbOAdBsZD6gd_q3LJl0I_jIE1dwOJFkZAkXS4hGJ0-RmNtWWNxNpyBmNK226ZDcbo8DnV5Td0Mbv4pr-yYRN12m8G1xv-SXEfzgU6_vrUXEYmA2BIamV_CrH-2a-ilxarCOofpHHGX0V44NSLMXRNKpV2Ig8fAz_6b8BewicJmkCAAA=

code
App.svelte
<script>
  let bool = true;
  let count1 = $state(0);
  let count2 = $derived(bool ? count1+1 : count1+2);
  let count3 = $derived.by(() => {
    if (bool) {
      return count1 + 1;
    } else {
      return count1 + 2;
    }
  });

  function increment1() {
    count1 += 1;
    console.log(`${count1}:${count2}:${count3}`);
  }
  function toggle() {
    bool = !bool;
    console.log(`${count1}:${count2}:${count3}`);
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={toggle}>toggle</button>

複数の$state

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn2RzWrDMBCEX2UROcg0JLWTk_-gz1EXksibImqvjLwKBKN3L47sJjVt0GU1M_tpQIM46wZ7kb4Pgo4tilS8dZ1YC75246W_YMMo1qI3zqpRyXtldcdlRRU3yKCMI46hgFXPR0b5GmW_rOR_azdaNVp9wVpOnJdpaxHdP0Q3p6uUERQlDGNmPBbZWYIF44bwNxJVrAz1DMZx5xgKCIRRNA1uGvMpD6shAHw6TYnPYBp3P-LeH0K5syPF2hBoUhZbJI5lNHeaqxQQZ0EJL8uw6_8GJAtA8gyQb-9fQfnJMRsCQ6rR6qsY7qV8Gbw434ahfBZP5njyEBdr0ZpanzXWImXr0H_4b52-umE7AgAA

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = $state(0);
  let count3 = $derived(count1 + count2);
  let count4 = $derived.by(() => {
      return count1 + count2;
  });

  const output = () => console.log(`${count1}:${count2}; ${count3}:${count4}`);
  function increment1() {
    count1 += 1;
    output();
  }
  function increment2() {
    count2 += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>

反応する$state

オブジェクト

  • 基本

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn2R2WrDMBBFf2UQeVCocdYnb9DvaAqx5XERcTRGHgWC0b8XRc3SEIJeNFd3Dgc0iU73OIrsaxKmPqLIxOcwiETweQjDeMKeUSRiJGdVSIpRWT1wtTM77pFBkTMMJcxGrhnl1BFlsEygqW0GSz_P_xU3odmi1Sds5SVJOyL4iK9pU9unhe3DQtqcpZxDWcEUOuFYZGcNvCRdQFHA7FiRGRnI8eCCbuSEkHpMe_qR-9l0w_jsOjS19Tn8TZtbvvX7KNo5o1iTAW2UxSMaXsn51e9Bq4RVHsOoIOO6f81YPzOa2r5jFIv7r5iiccxkgIzqtTqU013NV_FtVSzipXpXX1_r64e6SMSRWt1pbEXG1qH_9r9hFDADRgIAAA==

code
App.svelte
<script>
  let count = $state({foo: 0, bar: 0});
  let count3 = $derived(count.foo + count.bar);
  let count4 = $derived.by(() => {
      return count.foo + count.bar;
  });

  const output = () => console.log(`${count.foo}:${count.bar}; ${count3}:${count4}`);
  function increment1() {
    count.foo += 1;
    output();
  }
  function increment2() {
    count.bar += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>
  • 構造変更・再代入

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACpWSW27DIBBFtzJC_cCq5aRJvvySuoIuoKkUDOPKLQELQ5TUYu8VtuMkVR-q-GGGM_fOAD2pG4kdSZ97otgeSUoe25bExJ7aEHQHlBZJTDrtDA-ZvOOmaW25VVsr0QLXTlko4K6zzCLta61TWMZQMZPC0kdZALlW3YSuoIARWk3Qymc3YuugJtA0BxT0qXpDbpMDkw47OpxHiUHhOFLKOI_hGEFRAuP8_hjDMopuxTZXYkl1onSg-8CEZdA6o-DfLoPJONw8nna2deEqRo-Q1BITqV_p7q4fRJNaa5-eg4qZ6-DDZzBF6zm_8bvRrHaK20YrYELQ6DzBXAsFPGRjbuyDTj3e1AqUl1qBEi3CLPF3ucFWMo5f7KGYXvYHgXxx-TIqr5y1WoFWXDb8veiZEL5kQuSL8aT8hhEofSlQ_sZMvfly2lyxJCZ7LZq6QUFSaxz6F_8JpnStwPkCAAA=

code
App.svelte
<script>
  let count = $state({foo: 0, bar: 0});
  const count2 = {foo: 2, bar: 2};
  let count3 = $derived(Object.values(count).reduce((acc, x) => acc+x, 0));
  let count4 = $derived.by(() => {
      return Object.values(count).reduce((acc, x) => acc+x, 0);
  });

  const output = () => console.log(`${count.foo}:${count.bar}:${count.baz}; ${count3}:${count4}`);
  function add() {
    count.baz = 1;
    output();
  }
  function del() {
    delete count.baz;
    output();
  }
  function replace() {
    count = count2;
    output();
  }
</script>

<button onclick={add}>add</button>
<button onclick={del}>del</button>
<button onclick={replace}>replace</button>

配列

  • 基本

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn2R3YrCMBCFX2UIXkS2qFWv-gf7HLWgpuMStk5KOhGk5N2XmO3qikhuZs45-XIgozjpDgeR1aOgwxlFJj77XiSCr31Yhgt2jCIRg3FWBaUYlNU9VzvacYcMyjhiKGE28IFR1qsEVs08_2dvgt-i1Rds5U2pVw18RLNOn-Pbh_jieJVyDmUFY8iEY5GdJXjBuWH8jUY7VoYGBuO4d6FgpATRdLjozJfcz8YJ4rNpThufw--y-ZO3fh9Lnhwp1oZAk7J4RuJUzqdu90olpHnU4vsy3vavEetnRPoWUSzvf0DF0TEbAkOq0-q7HO_FfBW9tFjGoXoXX0_x9UNcJOJsWn3S2IqMrUPf-B85ySa-NAIAAA==

code
App.svelte
<script>
  let count = $state([0, 0]);
  let count3 = $derived(count[0] + count[1]);
  let count4 = $derived.by(() => {
      return count[0] + count[1];
  });

  const output = () => console.log(`${count[0]}:${count[1]}; ${count3}:${count4}`);
  function increment1() {
    count[0] += 1;
    output();
  }
  function increment2() {
    count[1] += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>
  • 構造変更・再代入

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACo1RXY-CMBD8K5vGh5IjiugTAsn9DiQR2_WOXG2b0hoN6X-_FNDT-04fujs7M51me3JoBXYkq3oimyOSjDxrTWJiLzo03QmFRRKTTjnDApJ3zLTallu5tQItMOWkhQJmnW0s0iqJIamjTRgzJbuJkEIBVRpDWm8ehKug5GjaE3I6IHOD3DGktGEshnMERQkNY0_nGJIoelSv79Tz_YXSgd0HTjgGrTMS_rYdXP1w3WIrZ7ULHxtNA6gEzoV6obtZP3hWSe2za728q9Pab2BqVjd47XfjSwcnmW2VhIZzGl3zjjG1617pcuRt7RiCTgEftBzFF63S9B9Kg1o0DD-poZg29YNBvvhYvMz3zlolQUkmWvZW9A3nvmw4zxfjpPyGw1H4kqP4jTNl8-VU3HFJTI6Kt4cWOcmscehr_w6PBYitvwIAAA==

code
App.svelte
<script>
  let count = $state([0, 0]);
  const count2 = [2, 2];
  let count3 = $derived(count.reduce((acc, x) => acc+x, 0));
  let count4 = $derived.by(() => {
      return count.reduce((acc, x) => acc+x, 0);
  });

  const output = () => console.log(`${count[0]}:${count[1]}:${count[2]}; ${count3}:${count4}`);
  function add() {
    count.push(1);
    output();
  }
  function del() {
    count.pop();
    output();
  }
  function replace() {
    count = count2;
    output();
  }
</script>

<button onclick={add}>add</button>
<button onclick={del}>del</button>
<button onclick={replace}>replace</button>

クラス

  • 基本

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn2R3WrDMAyFX8WYXqSspL9XaRIYe4x50MRRhpljB1vuKMHvPhw3WSild9KR9PlYGmgrJFiafQ5UVR3QjL73Pd1QvPUhsVeQCHRDrXaGByW33IgeSacbJ6FkiiGXlbXkQzuFZAgCw1ZrUpCVxQoh2a3PUa0rQwqyGzPPVL6NrJKpfI4YSkDCR1hBFPxGcBIZc-0Y8A0YcYUmGZU0vPkWq2ldmYeB02IgrW9JsiZFOdllaACdUeQpKfodeeG3Wlkk2mHvgsPICaKWkEr9nVxWw4zx2ZTUlfFncs-Os37yl2i0dYqj0IoIxQ10oHCfrCd_C1sF2d-3GS3cF-OfMw6PjHCCF4zFSZjKa4eoFdGKS8F_iuHfmi9jbZ9vY1C-aj9M7YdFO93QTjeiFdDQDI0D_-X_ALZXPLOPAgAA

code
App.svelte
<script module>
  class Count {
    foo = $state(0);
    bar = 0;
  }
</script>
<script>
  let count = new Count();
  let count3 = $derived(count.foo + count.bar);
  let count4 = $derived.by(() => {
      return count.foo + count.bar;
  });

  const output = () => console.log(`${count.foo}:${count.bar}; ${count3}:${count4}`);
  function increment1() {
    count.foo += 1;
    output();
  }
  function increment2() {
    count.bar += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>
  • 再代入

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACo2R3WrDMAyFX0WYXris9G-7apPA2GMsg6aOupm5drDljhL87sNxmmalG7uKdXT0cRS17CAVOrZ5bZmujsg27Llp2IzRuYmFO6EiZDPmjLciKpkTVjYER1N7hUWpSxKqcg5ejNcEbRRKOhgDOUwcVYR8Od0mdV9ZyGHZV8JoR9YLMpZLLWl6GS6JPqSbJ0bsbH_oiTLSQ_yEUmeLlK0odTa8SlJIILpwQyKNXykvX05TuC5Msq0hh6thlfoD5DFSarTyhDXvlC7oQ-rGcDcDT6OB-f7M-RTy4rqqRfJWw11SBwodb4hoPDU-rpI4UTQK58q8892kHTBhcyn2lQ1b6KvHQX8KuxT04LUgaTRILSweUdOKD6cYxcph1f_vFIGn8XCfsb5lxKv9j2GxUZXAGwDk_XV-AYyOX-ps74mMBqOFkuIzb6-7hSL1VtkiPYq_7OuLff2XvU8civ4x8rIZO5paHiTWbEPWY3gL38kpEI91AwAA

code
App.svelte
<script module>
  class Count {
    foo = $state(0);
    bar = 0;
    constructor(init) {
      this.foo = init;
      this.bar = init;
    }
  }
</script>
<script>
  let count = $state(new Count(0));
  const count2 = new Count(1);
  let count3 = $derived(count.foo + count.bar);
  let count4 = $derived.by(() => {
      return count.foo + count.bar;
  });

  const output = () => console.log(`${count.foo}:${count.bar}; ${count3}:${count4}`);
  function increment1() {
    count.foo += 1;
    output();
  }
  function increment2() {
    count.bar += 1;
    output();
  }
  function replace() {
    count = count2;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>
<button onclick={replace}>replace</button>

検知スコープ

  • 基本

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn1R3WqDMBR-lRB6EVlpZ9or_2DPMQe18TjCNJF40lEk7z5s1DrXlVzknO_nnC-kp5WsoaPRe09V0QCN6Fvb0i3Fazs03QVqBLqlnbZGDEjSCSNbzHKVYw1IhLYKQ5KSTYcFAnsN4l8U_586DFQJRl6gZN-maEMWrCTHhWR3vjIWkDQj_aAZjgG0RpHRe7M6f1VWCZR65ibP6BhTv9xoPln_GPljI5_lKkehVYdEW2wtkpT4hAOoa9jV-pOdNr1f56Kx4i4mY3mYwaM7raJLJQw0oHCRfwqekjD2iN_88AnzAL4awJ8NSPb3L1bJ2SJqRbQStRRfaX8P5TLPhcneF9kzOZ_kfCGnW9roUlYSShqhseA-3A-SZs7bkwIAAA==

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = $state(0);
  let count3 = $derived(wrap1());
  let count4 = $derived.by(() => {
      return wrap1();
  });
  function wrap1() {
    return count1 + wrap2();
  }
  function wrap2() {
    return count2;
  }

  const output = () => console.log(`${count1}:${count2}; ${count3}:${count4}`);
  function increment1() {
    count1 += 1;
    output();
  }
  function increment2() {
    count2 += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>
  • untrack

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn1S3WrDIBR-lYP0wrDSLrZX-YM9xzJoasyQJhrMsaME3304k7TruuKFx_P9nPOBI2lkKwaSvI9EVZ0gCXnre7ImeOn9YziLFgVZk0Fbw30nG7iRPRalKlF2vTYII1iFpuIncNAY3UE56UqSelorELi2CmPIYTVghYK-Rr8h9j-081AtjDyLmn6Zqo9pdEfZ31A2xwulEeQFjJ7jjxFojYJJ-yN14Wqs4ij1gs2aSTFt_TIHpDTKC09l8wrujwt77MIWuiqRazUgaIu9RcghrOubuhWbVn_Sw2oMs10yVcylMJW7pbl3h7scUnEjOqHwJsycIoc4DZ0wmT6KsBiwOwP2zCDbXr-Fyo4WUSvQireSn_LxupQrAhZn21AUz-hsprMbOlmTTteykaImCRor3If7BvimhNLHAgAA

code
App.svelte
<script>
  import { untrack } from "svelte";
  let count1 = $state(0);
  let count2 = $state(0);
  let count3 = $derived(wrap1());
  let count4 = $derived.by(() => {
      return wrap1();
  });
  function wrap1() {
    return count1 + untrack(()=>wrap2());
  }
  function wrap2() {
    return count2;
  }

  const output = () => console.log(`${count1}:${count2}; ${count3}:${count4}`);
  function increment1() {
    count1 += 1;
    output();
  }
  function increment2() {
    count2 += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>

$derived内で他変数変更

$derived

  • 複数式
    コンパイルエラーになる。

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAClWOUQuCQBCE_8qy9GAk2NWbqdDv6Hqwc4Uj3ZO7vSDE_x5mJb3NzjfMzoit7ShgfhmR654wx_MwYIryHOYjPKgTwhSDi97MThGMt4NUmrV0JGBcZFFQwiZILZTst6c_dIAS9v_WcU435O2DmuQX-hTtQJ1WfXi3sZY2shHrGCwbTz2xqGQL44y0fOMlqPenSXORrTu5uEURx-DYdNbcy3EtmaqFqSJbRKUZU-xdY1tLDebiI03X6QVGoxYUKQEAAA==

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = 0;
  let count3 = $derived(count2 = count1 + 1; count1 + 2);  // compile error

  function increment1() {
    count1 += 1;
  }
</script>

<button onclick={increment1}>button1</button>
  • 単一式
    この方法の場合、実行時エラーになる。

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAClVQ24qDMBD9lSH0IdLSqn2zKux3qFA3jkvYOBGdlF3Ef1_SFLu-zZwz58IsotcGZ5FVi6B2QJGJj3EUJ8G_o1_mBxpGcRKzdZPySD6rSY9c1lSzQQZlHXECBRxmbhllFTfRbUemUEC8h67-vsNJP7CTweE8tKP8gaKExZ_WvGlfCUdIbv8YD6RPYI2egeQJmhms49ExFCAjb-dBa_Bs7Je8H5YgruJmzV5Luk1XD99D_d6RYm0JNKkJByROZLTrlsCx2DqFUBm0a0355f0nyj8dsyWwpIxW38Xy9lzLwCX5JQxlTeIkBtvpXmMnMp4crs36B6eIbKCpAQAA

code
App.svelte
<script>
  let count1 = $state([0]);
  let count2 = 0;
  let count3 = $derived(count1.map(x => {
    count2 = count1 + 1;
    count1 + 2;
  }));

  const output = () => console.log(`${count1[0]}:${count2}:${count3[0]}`);
  function increment1() {
    count1 += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>

derived.by

  • 通常の変数
    更新されるが挙動が直感的でない印象。

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAClVQ0YqDMBD8lSX0IdLSVvtmVbjvOA_axvUIFzcSN4Ui-fcjTVF8m53ZmR12Fr02OInyexZ0H1CU4mscxUHwa4zD9ETDKA5ist6pyFSTcnrkpqWWDTIo64lzqGE38Z1RnrPrRiqghvOWusTtDp1-Ynd8vKTMoG5gjjstL6ZP8B7ya1Icsne08sWbD-97FI00MVjPo2eoIYVG0ho8Gvsrb7s5eUP5QcWCLuGWeveeFGtLoEk5HJA4l9mmWw77eumUzsnkDS1Vp_U9VD08syWwpIxWf_W8ZoYmaXl1SqBpSRzEYDvda-xEyc5j-An_1YzStKABAAA=

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = 0;
  let count3 = $derived.by(() => {
    count2 = count1 + 1;
    return count1 + 2;
  });

  const output = () => console.log(`${count1}:${count2}:${count3}`);
  function increment1() {
    count1 += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
  • $state変数
    更新されるが挙動が直感的でない印象。代入されるだけの$state変数は変更検知されない模様。

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn1QXWvCMBT9KyH4kKLoGt9qW9jvWAdqejvC2puS3AhS8t9HjGvn2Hw7OV_3kIl3ugfHi7eJ42kAXvDXceQbTtcxPtwFegK-4c54qyJTOmX1SHWDDfVATBmPlLOKrRydCMRLdniQ5P_SPkotWH2Bdnu-CpGxqmZT9DQ0h-8H1iw_JMUCeYsLL298uJVjDKIjZjyNnljFUmkkTQ_b3nyI42pK2VDckZzRPhzTyM6jIm2QaVQWBkDKRfawLWfrat6UzomUDX8XyF8F8llBuVv-GcuzJzLIDKpeq89qWkaFOml5uUugfmaX33b5w843fDCt7jS0vCDrIbyHL2QC3O4YAgAA

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = $state(0);
  let count3 = $derived.by(() => {
    count2 = count1 + 1;
    return count1 + 2;
  });

  const output = () => console.log(`${count1}:${count2}:${count3}`);
  function increment1() {
    count1 += 1;
    output();
  }
  function increment2() {
    count2 += 1;
    output();
  }
</script>

<button onclick={increment1}>button1</button>
<button onclick={increment2}>button2</button>

$derived.byの実行タイミング

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACn1S226DMAz9FSvqA6hVKd0bBaR9xzKpNBgaFRyUmE4V4t8nLhrt1u7N8bnYPkonCl2hE9FHJyirUUTivWnERvCtGR7uihWj2AhnWquGTuyU1Q2nkiRXyKBMSxxCAivHGaO38w8P0B4S2D223gZ2jlZfMd-ebp7nQ5JCN3AkK0POVLitTOlJMbOg0BalmKwHzmw8D19DePhPjZQvYovcWlqk-7HfjzBJLlpSrA2BJmWxRuLQ858vh1ck_rPaPWOMBLQDdc6o1FT-uiGEdfJi90n6lc1azJ_POGZKoXPABuZrI1h1U8z90T8ABAGwKcsKQZl6uAfOaPGJ0ywL-2iu9j_VaPU6grt8e0lxsHwRik8tsyEwpCqtLkm3xNqnExbGwVSkksRG1CbXhcZcRGxb7D_7bxHj0e-kAgAA

code
App.svelte
<script>
  let count1 = $state(0);
  let count2 = 0;
  let count3 = $derived.by(() => {
    console.log("derived fire");
    count2 = count1 + 1;
    console.log("derived end");
    return count1 + 2;
  });

  function increment1() {
    console.log("event fire");
    console.log("state is changing");
    count1 += 1;
    console.log("state was changed");
    console.log(`access to derived: ${count3}`);  // toggle comment here
    console.log(`${count1}:${count2}:${count3}`);
    console.log("event end");
  }
</script>

<button onclick={increment1}>button1</button>

コンポーネント構成例

  • SelectPanel

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACpVTyY6cMBD9lYozByMhmCQ3GhjlknOkHNstDUvR44zbdmwzIkH-98hsjTTkkFv51f5eeSQdF2hJdh6JrG5IMvJVaxIT91uHh31D4ZDExKreNAHJbWO4diWTzPGbVsbBDxTYuO-VRAGdUTdgJEl3YDJXYeQUkgQ6ENw6KODBusohPTPSKcVIDIzUlVmNP7Pxqx8YuURbrkHbi3324ut62TiuJCjZCN680gjGgDMXuiVaaToHeibz9L6FzOveOSUhrFwwMr8YgXEp5Eut9DRyns7Okslcl-M8SSJQXt0L5AU8whMwIhXYaXlsGYFsGTj5qbikjJyAkcjnqZ5777mruWyziZrJWhZNp0ASk5tqecexJZkzPfp4U2xX4z-VG6GXzlTNK_hVuXdajZtaYaqqFkijeKfCHQUf3toobeldL6euV4E2uFo0_A1bOglyqzQdoCiBjqKqUWQwxFArJTLoKmHRR9FURDLXKGkdqN7pPrSkUUgLoBKYCHWlzw_j0ub8eHlKQhWf3bFPB9jnA-zLij0fn9Q3ymWLw3ZYBl1v5DLPgjG3VptiL1NBKODDAXxaUzY2l6Ck48KhmfkZptjoTtiQTIRFW_rMDF0Bf3Tl40esmpdNjcouZgzcMwnwr1-wrF6Mdw4iXy6UzYP43b8Y09DGHx3sxf8FNC9tBW4EAAA=

code
App.svelte
<script>
  import SelectPanel from "./SelectPanel.svelte";
  let list = $state(["foo", "bar", "baz", "qux"]);
  let result = $state([]);
  function onclick() {
    list.pop();
  }
</script>

<button type="button" {onclick}>pop list</button>
<p>{result.length <= 0 ? "no selected" : result.join("; ")}</p>

<SelectPanel bind:list bind:result />
SelectPanel.svelte
<script>
  import { untrack } from "svelte";
  let { list = $bindable(), result = $bindable() } = $props();
  let toggles = $derived(list.map(x => ({label: x, bool: false})));

  const output = () => console.log(`${toggles[0]?.bool}:${toggles[1]?.bool}:${toggles[2]?.bool}:${toggles[3]?.bool}`);
  function onclickF(index) {
    return () => {
      toggles[index].bool = !toggles[index].bool;
      result = toggles.filter(x => x.bool).map(x => x.label);
      output();
    }
  }
</script>

{#each toggles as toggle, i}
  <button type="button" onclick={onclickF(i)}>{toggle.label}</button>
{/each}

所感と雑記

以前は$derived変数はオブジェクトであってもfreezeされているような状態だった気がします。そのために仕方なしに$effect等で迂回していたのですが、迂回不要になったようです。$state.freezeが廃止され$state.rowに変わった辺りで変わったのか...勘違いでなければですが。リリース前に使用しているので仕方ないですね。
リリースと言えば、5.0 Milestoneの残りOpen数が8 (98% complete)で、もうすぐSvelte5がリリースされそうなので楽しみです。

参考文献

  • なし

Discussion