ReactのuseRef()の動作

useRef()、再レンダリングせずコンポーネント単位の状態を持てるという2つのメリットがあります。
非エクスポート変数を使った場合、useState()を使った場合、useRef()を使った場合を見ていきます。

サンプルはA, B, Cの三つのボタンを作り、それぞれAを3回、Bを3回、Cを3回の順でクリックしたとします。

非エクスポート変数

let counter = 0;

const CountForm = ({label}: {label: string}) =>
{
    const updateCounter = () =>
    {
        counter++;
        console.log(`[ ${label} ] ${counter}`);
    }

    console.log("CountForm Rendering");

    return (
        <div>
            <input type="button" value={`COUNT ${label}`} onClick={e => { updateCounter() } } />
        </div>
    )
}

const App = () =>
{

    return (
        <>
            <CountForm label="A" />
            <CountForm label="B" />
            <CountForm label="C" />
        </>
    )
}

export default App;
CountForm Rendering 
CountForm Rendering 
CountForm Rendering 
[ A ] 1 
[ A ] 2 
[ A ] 3 
[ B ] 4 
[ B ] 5 
[ B ] 6 
[ C ] 7 
[ C ] 8 
[ C ] 9 

ボタンを3つ作ってる時点で3つのログが表示されます。
counterはそれぞれのコンポーネントに共有されているので、どのボタンを押してもカウントされてしまいます。
再レンダリングこそされませんが、コンポーネントごとにcounterを持ちたい場合は不利です。

useState()

import { useState } from "react";

const CountForm = ({label}: {label: string}) =>
{
    const [counter, setCounter] = useState(0);

    const updateCounter = () =>
    {
        setCounter(counter + 1);
        console.log(`[ ${label} ] ${counter}`);
    }

    console.log("CountForm Rendering");

    return (
        <div>
            <input type="button" value={`COUNT ${label}`} onClick={e => { updateCounter() } } />
        </div>
    )
}

const App = () =>
{

    return (
        <>
            <CountForm label="A" />
            <CountForm label="B" />
            <CountForm label="C" />
        </>
    )
}

export default App;
CountForm Rendering 
CountForm Rendering 
CountForm Rendering 
[ A ] 0 
CountForm Rendering 
[ A ] 1 
CountForm Rendering 
[ A ] 2 
CountForm Rendering 
[ B ] 0 
CountForm Rendering 
[ B ] 1 
CountForm Rendering 
[ B ] 2 
CountForm Rendering 
[ C ] 0 
CountForm Rendering 
[ C ] 1 
CountForm Rendering 
[ C ] 2 
CountForm Rendering 

counterをレンダリングする場合は意図したとおり動くのですが、その場でログを出した場合は0からのカウントになります。
setCounter()で更新しても次の再レンダリングがされるまでcounterは更新されない点に注意してください。
状態を変更するたびに再レンダリングが走っているのが確認できます。

useRef()

import { useRef } from "react";

const CountForm = ({label}: {label: string}) =>
{
    const counter = useRef<number>();

    const updateCounter = () =>
    {
        counter.current = (counter.current || 0) + 1;
        console.log(`[ ${label} ] ${counter.current}`);
    }

    console.log("CountForm Rendering");

    return (
        <div>
            <input type="button" value={`COUNT ${label}`} onClick={e => { updateCounter() } } />
        </div>
    )
}

const App = () =>
{

    return (
        <>
            <CountForm label="A" />
            <CountForm label="B" />
            <CountForm label="C" />
        </>
    )
}

export default App;
CountForm Rendering 
CountForm Rendering 
CountForm Rendering 
[ A ] 1 
[ A ] 2 
[ A ] 3 
[ B ] 1 
[ B ] 2 
[ B ] 3 
[ C ] 1 
[ C ] 2 
[ C ] 3 

簡単にコンポーネントごとにcounterを持つことが出来るようになりました。
しかも再レンダリングはされません。

useRef()から得られるオブジェクトはcurrentプロパティに値を保存できます。
デフォルトはundefinedが設定してあり、寿命はコンポーネントの生存期間になります。
currentconsole.log()で監視すると最初はundefinedが設定されることが分かるのでやってみてください。

ではコンポーネントが輪廻転生したらどうなるでしょう!?

再マウントされるとcurrentは初期化される

import { useRef, useState } from "react";

const CountForm = ({label}: {label: string}) =>
{
    const counter = useRef<number>();

    const updateCounter = () =>
    {
        counter.current = (counter.current || 0) + 1;
        console.log(`[ ${label} ] ${counter.current}`);
    }

    console.log("CountForm Rendering");
    console.log(counter.current);

    return (
        <div>
            <input type="button" value={`COUNT ${label}`} onClick={e => { updateCounter() } } />
        </div>
    )
}

const App = () =>
{
    const [flag, setFlag] = useState(true);

    return (
        <>
            <div>
                <input type="button" value={flag ? "Let's Unmount" : "Let's Mount"} onClick={e => setFlag(!flag)} />
                <p>{flag ? 'Mounted' : 'Unmounted'}</p>
            </div>

            {
                flag ?
                <>
                    <CountForm label="A" />
                    <CountForm label="B" />
                    <CountForm label="C" />
                </>
                :
                <p>Un mounted buttons!</p>
            }

        </>
    )
}

export default App;

操作は3つのボタンを3回ずつクリック
Let's Unmountをクリックしてアンマウント、
Let's Mountをクリックして再マウント
さらに3つのボタンを3回ずつクリック

CountForm Rendering 
undefined
CountForm Rendering 
undefined
CountForm Rendering 
undefined
[ A ] 1 
[ A ] 2 
[ A ] 3 
[ B ] 1 
[ B ] 2 
[ B ] 3 
[ C ] 1 
[ C ] 2 
[ C ] 3 
CountForm Rendering 
undefined
CountForm Rendering 
undefined
CountForm Rendering 
undefined
[ A ] 1 
[ A ] 2 
[ A ] 3 
[ B ] 1 
[ B ] 2 
[ B ] 3 
[ C ] 1 
[ C ] 2 
[ C ] 3 

再マウントするとcurrentが再びundefinedになっていることが確認出来ます。

実行結果はこちらです。

certificate Docker Gutenberg Hyper-V openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク