Reactの再レンダリングが起きる仕組みについて学ぶ
学んだこと
再レンダリングの仕組み
- State の値を変更した際に再レンダリングを行い、処理をもう一度実行している。
- 以下の処理の場合、on/off ボタンを押したときに
import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(true);
const onClickButton = () => {
setNum((prev) => prev + 1);
setNum(num + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};
- console でレンダリング回数に関して調べてみる。
- この場合、
console.log("--------App---------")
が 2 回表示されていた。- 理由としては、開発時のみ 2 回レンダリングを行っているから。
- render を呼び出す再に strictmode を付与している且つ、開発ビルドだと 2 回レンダリングが行われる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
console.log("--------App---------")
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(true);
const onClickButton = () => {
setNum((prev) => prev + 1);
setNum(num + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};
- コンポーネントも確認する。
- コンポーネント内に console.log を配置し確認する。
- on/off ボタンを押して state を更新する。
- 結果は以下の通りだった。
--------ColorfulMessage---------'
が 2 回表示された。- 理由としては、state を更新すると関連するコンポーネントも再レンダリングが実行される。
- 再レンダリングの結果、App 内の処理が再度呼び出された。
- ColorfulMessage を呼び出している箇所が 2 箇所あるため、その分 conosle.log が呼ばれた。
- 結果は以下の通りだった。
export const ColorfulMessage = (props) => {
console.log('--------ColorfulMessage---------');
const contentStyleA = {
color: props.color,
fontSize: '18px',
};
return <p style={contentStyleA}>{props.children}</p>;
};
import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
console.log("--------App---------")
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(true);
const onClickButton = () => {
setNum((prev) => prev + 1);
setNum(num + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};
簡単なプログラムを作成する。
- 3 の倍数のときは\(^o^)/を表示するプログラムを作成する。
- 3 の倍数のときは、\(^o^)/を表示できているが、on/off ボタンを押しても反応しないバグが発生している。
- num = 3 の場合 on/off ボタンを押すと以下の処理が行われる。
- onClickToggle が呼ばれ、isShowFase = true のため、isShowFace = false になる。
- 再レンダリングが実行される。
-
if(num % 3 === 0)で true になり、isShowFace setIsShowFace(true)が処理される。 -
isShowFace setIsShowFace(true)では isShowFase = false のため、setIsShowFace(true)が評価される。 - 結果\(^o^)/が表示されたままになる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
console.log('--------App---------');
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(false);
const onClickButton = () => {
setNum((prev) => prev + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
// 3の倍数の場合は\(^o^)/を表示するように処理
if (num > 0) {
if (num % 3 === 0) {
isShowFace || setIsShowFace(true);
} else {
isShowFace && setIsShowFace(false);
}
}
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};
バグを解決するためには…
- useEffect を使う必要がある。
useEffect の挙動について知る
- この場合、on/off ボタンを押すと、
------useEffect---------
が呼び出されていた。 - そのため、state の更新タイミングで useEffect が呼ばれている事がわかる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
console.log('--------App---------');
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(false);
const onClickButton = () => {
setNum((prev) => prev + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
useEffect(() => {
console.log('------useEffect---------');
});
if (num > 0) {
if (num % 3 === 0) {
isShowFace || setIsShowFace(true);
} else {
isShowFace && setIsShowFace(false);
}
}
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};
- useEffect に空の配列を渡してみる。
- 最初のレンダリングは一律で呼び出される。
- すると、最初のレンダリングは useEffect を呼び出しているが、state 更新タイミングでは呼び出されていなかった。
- なので、再レンダリング時に都度呼び出したくない場合は、空の配列を渡す必要がありそう。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
console.log('--------App---------');
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(false);
const onClickButton = () => {
setNum((prev) => prev + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
useEffect(() => {
console.log('useEffect');
}, []);
if (num > 0) {
if (num % 3 === 0) {
isShowFace || setIsShowFace(true);
} else {
isShowFace && setIsShowFace(false);
}
}
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};
- useEffect の配列に num を渡すとどうなるか
- 最初のレンダリング時は一律で呼び出される。
- state の num が更新されるタイミングで
--------useEffect---------
が呼び出されていた。 - useEffect に state を渡すと、state が変更のあったタイミングで呼び出されるようになる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
console.log('--------App---------');
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(false);
const onClickButton = () => {
setNum((prev) => prev + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
useEffect(() => {
console.log('--------useEffect---------');
}, [num]);
if (num > 0) {
if (num % 3 === 0) {
isShowFace || setIsShowFace(true);
} else {
isShowFace && setIsShowFace(false);
}
}
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};
useEffect を使って on/off の挙動を修正する
-
useEffect 内に 3 の倍数時の判定処理を入れるようにした。
- isShowFace を変更しても、3 の倍数時の判定処理を通らなくなった。
- 理由としては、useEffect 内の処理は num の更新タイミングのみ処理が実行されるため isShowFace が更新されても useEffect の処理は行われない。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
console.log('--------App---------');
const [num, setNum] = useState(0);
const [isShowFace, setIsShowFace] = useState(false);
const onClickButton = () => {
setNum((prev) => prev + 1);
};
const contentStyleA = { color: 'blue', fontSize: '18px' };
const contentStyleB = { color: 'green', fontSize: '18px' };
const onClickToggle = () => {
setIsShowFace(!isShowFace);
};
useEffect(() => {
if (num > 0) {
if (num % 3 === 0) {
isShowFace || setIsShowFace(true);
} else {
isShowFace && setIsShowFace(false);
}
}
}, [num]);
return (
<>
<h1 style={contentStyleA}>こんにちは!</h1>
<ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
<ColorfulMessage color="green">元気です!</ColorfulMessage>
<button onClick={onClickButton}>カウントアップ</button>
<p>{num}</p>
<button onClick={onClickToggle}>on/off</button>
{isShowFace && <p>\(^o^)/</p>}
</>
);
};