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
が設定してあり、寿命はコンポーネントの生存期間になります。
current
をconsole.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
になっていることが確認出来ます。
実行結果はこちらです。