Open4

SolidJSのSubscribersが分からない

KizitoraKizitora

https://docs.solidjs.com/concepts/intro-to-reactivity#subscribers

ReactのuseEffectと異なり,SolidJSはcreateEffectで第二引数を指定しなくても動作する.これはSubscribersという機能のおかげである.しかし,公式ドキュメントを理解しきれていないためこれが何なのか,どのような仕組みで動作しているのかが分からない.

先駆者が説明してくれているがこれも私の勉強不足で理解しきれてないため,時間をかける必要がある.

https://zenn.dev/fj68/articles/da3458abab5d1c#createeffect

KizitoraKizitora

少し読み進めてSubscribersに関する文言が出てきたので自分でかみ砕いでみる.


https://docs.solidjs.com/concepts/intro-to-reactivity#tracking-changes

Since initialization is a one-time event, if a signal is accessed outside of a tracking scope, it will not be tracked. To track a signal, it must be accessed within the scope of a subscriber. Reactive primitives, such as effects, can be used to create subscribers.

createEffectを使ってsubscribersを作成すると,状態が変化した場合に検知して動作できるようになる.初期化は一回きりなので,subscribersで追跡する必要がある.

逆に,subscribersが追跡できていない例を下記に示す.

Counter.tsx (not tracking)
import { Component } from "solid-js";
import { createSignal, createEffect } from "solid-js";

const Counter: Component = () => {
    const [count, setCount] = createSignal(0);
    const increment = () => setCount((count) => count + 1);

    console.log(count()) // countを追跡できてないので初回に実行され"0"とだけ表示される

    return (
        <button type="button" onClick={increment}>
            Button: {count()}
        </button>
    );
}

export default Counter;

これを実行するとconsole.log(count())は初回だけ実行され,その後はいくらボタンを押しても実行されない.これはsubscribersを使ってcountという状態を追跡できていないからだ.

つぎに,subscribersが追跡できている例を下記に示す.

Counter.tsx (tracking)
import { Component } from "solid-js";
import { createSignal, createEffect } from "solid-js";

const Counter: Component = () => {
    const [count, setCount] = createSignal(0);
    const increment = () => setCount((count) => count + 1);

    createEffect(() => {
        console.log(count())
    });

    return (
        <button type="button" onClick={increment}>
            Button: {count()}
        </button>
    );
}

export default Counter;

これを実行するとボタンを押したごとにconsole.log(count())が実行される.これはsubscribersを使ってcountという状態を追跡できているからだ.

まとめると,subscribersの追跡範囲に追跡したい状態が含まれているかどうかが重要になる.いまのところ,その追跡範囲に追加するにはcreateEffectの引数に追跡したい状態を指定すればよい.

KizitoraKizitora

追跡範囲に関して追記

https://docs.solidjs.com/concepts/intro-to-reactivity#updating-the-ui

The UI of a Solid application is built using JSX. JSX creates a tracking scope behind the scenes, which allows signals to be tracked within the return statement of a component.

JSXはreturn文内にあるシグナルを追跡できるよう,裏で追跡範囲を作成している.これにより,createEffectを使わなくてもシグナルを追跡でき,状態が変化したらUIも更新することができる.

Components, much like other functions, will only run once. This means that if a signal is accessed outside of the return statement, it will run on initialization, but any updates to the signal will not trigger an update.

一見,createEffectを使わなくてもシグナルを追跡できるのは当たり前に見える.しかし,コンポーネントは一度しか実行されないため,シグナルへのアクセスをreturn文の外で実行すると最初の一回だけ実行されてそれ以降は実行されない.そのため,JSXが追跡範囲を作成して実行後もreturn文の中身を追跡しておく必要がある.ちなみに,シグナルへのアクセスをreturn文の外で実行するケースは,前のコメントで提示したコードの通りである.

Counter.tsx
import { Component } from "solid-js";
import { createSignal, createEffect } from "solid-js";

const Counter: Component = () => {
    const [count, setCount] = createSignal(0);
    const increment = () => setCount((count) => count + 1);

    console.log(count()) // シグナルへのアクセスをreturn文の外で実行するケース!

    return (
        <button type="button" onClick={increment}>
            Button: {count()}
        </button>
    );
}

export default Counter;
KizitoraKizitora

createEffectの挙動について説明している「Effects」というタイトルのドキュメントを見つけたのでここに置いておく.

https://docs.solidjs.com/concepts/effects#managing-multiple-signals

もう少し調べたい気もするけど,いつまでもここで立ち往生してても時間がかかりそうなので一旦置いておく.公式ドキュメントも「Control Flow」を読み終えた次に「Effects」が来るので,そこまで読み進めようと思う.

ちなみに,下記の公式ドキュメントでもcreaetEffectの挙動について説明されているけど,こっちは上記のより昔に書かれたものなので,参考程度に留めておくのがいいかもしれない.日本語に訳されているというのはメリットだけど.

https://www.solidjs.com/docs/latest/api#createeffect