Javascript (React_基本的なこと03)

■useStateの挙動をいろいろとトライ。
useState、useImmerの挙動を見る。useStateのsetXXXは、レンダリングを呼び出すもので、呼び出されたときのスナップショットを撮るようなものとのこと。サンプルコードは単純にボタンを押してラベル部分が更新されるもの。


使うデータは下の通り。number, number2を上の図のボタンで使用。sampleValを下のオブジェクト部分で使用。

    const [number, setNumber] = useState(0);
    let [number2, setNumber2] = useState(0);
    const [sampleVal, setSampleVal] = useState({
        val_a: 'aaa',
        val_b: 'bbb',
    });
    const [sampleVal2, setSampleVal2] = useState({
        val_c: 'ccc',
        val_d: 'ddd',
        numval: { num_c: 11, num_d: 111 }
    });
    const [sampleVal2i, setSampleVal2i] = useImmer({
        val_ci: 'ccc',
        val_di: 'ddd',
        numval: { num_ci: 11, num_di: 111 }
    });

ボタンのコードは下の通り。
①.元の値に対して+1を3回繰り返す。
②.3を直接書き込み(エラーになる)。
③.4を入力。
④.元の値を2倍にする。
⑤.元の値を2倍にして、さらに+1する。
⑥.変数を演算してをそれを入力する。
⑦.入力した後にさらに演算を行う。
⑧.文字列を入力。
⑨.何も入力しない(setXXX()のみ)。
⑩.空白のみの入力。
⑪.スペースを入力。

エラーとなる②は、宣言を固定値(const)としているためのよう。number2で変数(let)とした場合、直接の書き込みでもエラーにはならなかった(setXXXの呼び出しをしなければレンダリングは起こらないけど)。⑨、⑩、⑪はいずれもラベルの存在自体がなくなったようにボタンのコンポーネントが上に詰められた。他は予想通りの挙動となった。

            <p>
                <div style={Theme1.theme}>
                    <h2>{number}</h2>
                    <button onClick={() => {
                        setNumber(function (n) {
                            return n + 1;
                        });
                        setNumber(n => n + 1);
                        setNumber(n => n + 1);
                    }}>1_Btnum_+3</button><br />

                    <button onClick={() => {
                        number = 3;
                    }}>2_Btnum_3_error</button><br />

                    <button onClick={() => {
                        setNumber(4);
                    }}>3_Btnum_4</button><br />

                    <button onClick={() => {
                        setNumber(n => n * 2);
                    }}>4_Btnum_*2</button><br />

                    <button onClick={() => {
                        setNumber(function (n) {
                            n = n * 2;
                            return n + 1;
                        });
                    }}>5_Btnum_*2_a</button><br />

                    <button onClick={() => {
                        let num = 5;
                        num = num * 3;
                        setNumber(num);
                    }}>6_Btnum_*variable</button><br />

                    <button onClick={() => {
                        let num = 5;
                        setNumber(num);
                        num = num * 3;
                    }}>7_Btnum_*variable2</button><br />

                    <button onClick={() => {
                        setNumber("aaaa");
                    }}>8_Btnum_aaaa</button><br />

                    <button onClick={() => {
                        setNumber();
                    }}>9_Btnum_noVal</button><br />

                    <button onClick={() => {
                        setNumber("");
                    }}>10_Btnum_noChar</button><br />

                    <button onClick={() => {
                        setNumber(" ");
                    }}>11_Btnum_Space</button><br />

                    <h4>{number2}</h4>
                    <button onClick={() => {
                        number2 = 3;
                    }}>1_Btnum2_let3</button><br />

                    <button onClick={() => {
                        number2 = 3;
                        setNumber2(number2);
                    }}>2_Btnum2_let3</button><br />
                </div>
            </p>


上では変数に対してuseStateを使うけど、次はオブジェクトに対するもの。


コードが下。
通常、オブジェクトに対して使う場合は、...sampleValのようにコピーした上で上書きするのが一般的な使い方のよう。sampleValの初期値が"aaa"(val_a), "bbb"(val_b)。

           <p>
                <div style={Theme1.theme}>
                    <h2>{sampleVal.val_a}, {sampleVal.val_b}</h2>
                    <button onClick={() => {
                        setSampleVal({
                            val_a: "aaa2"
                        });
                    }}>Button</button><br />

                    <button onClick={() => {
                        setSampleVal({
                            ...sampleVal,
                            val_a: "aaa2"
                        });
                    }}>Button_a</button><br />

                    <button onClick={() => {
                        sampleVal.val_a = "aaa2";
                        sampleVal.val_b = "bbb3";
                    }}>Button_val_a_b</button><br />

                    <button onClick={() => {
                        setSampleVal({
                            ...sampleVal
                        });
                    }}>Button_sampleVal</button>
                </div>
            </p>

一番上のものは、コピーを行っていないため、ボタンを押すと"aaa2"のみ表示され、val_bは何も表示されなくなる。2つ目は、コピーを行っているので"aaa2", "bbb"と意図通りの表示。3つ目は、setXXXを呼び出していないので、画面上は表示されない。ただ、直接入力を行ってもエラーにはならず、この後に4つ目の単純にコピーするだけのものを押すと、"aaa2", "bbb3"として表示される。更新だけさせて画面に反映させないといった使い方も出来そうだけど、おそらく推奨されるものではないと思う。

最後に入れ子のオブジェクト。
...sampleValのコピーはShallowとのことで、入れ子の先まではコピーされない。そのため、下のようにオブジェクト毎にコピーが必要。多少の入れ子なら問題ないけど、数が多くなると冗長になる。そこでImmerライブラリ中のuseImmerを使うと簡素化できるとのこと。下のボタンはそれぞれuseState, useImmerを使うものだけど、行っていることは同じ。

            <p>
                <div style={Theme1.theme}>
                    <h2>{sampleVal2.val_c},{sampleVal2.val_d},{sampleVal2.numval.num_c},{sampleVal2.numval.num_d}</h2>
                    <button onClick={() => {
                        setSampleVal2({
                            ...sampleVal2,
                            val_c: "ccc2",
                            numval: {
                                ...sampleVal2.numval,
                                num_c: 22
                            }
                        });
                    }}>Button3</button>
                    <h2>{sampleVal2i.val_ci},{sampleVal2i.val_di},{sampleVal2i.numval.num_ci},{sampleVal2i.numval.num_di}</h2>
                    <button onClick={() => {
                        setSampleVal2i(draft => {
                            draft.val_ci = "ccc2";
                            draft.numval.num_ci = 22;
                        });
                    }}>Button3_immer</button>
                </div>
            </p>

ここまでチュートリアルを見てきてuseStateの記述が多い。これが基本になる部分かと思う。