最近Reactにハマッています。
仮想DOMフル使用、やたら難解なライフサイクルフックの仕様など少々時代に逆行気味な気もしますが、豊富なエコシステムとシンプルなjsx記法はやっぱり書いてて楽しいですね。
Hooksを始めとするReactの仕様を正しく理解してデータの取り扱いを工夫して無駄なレンダリング・繰り返し処理を減らし、少しでも動作を軽くするために実装の改善案をまとめてみました。
1. コンポーネント内での初期データ取得・加工は極力避ける。
こちらの記事でも言われていますが、「ReactはあくまでUIライブラリだからUI以外の事はやるべきではない」というのは鉄則です。
Webアプリの初回起動時にREST APiやサーバー内のenvファイル(TauriやElectronならconfigなど)を非同期処理で読み込んでデータを取得し、
アプリに必要なデータを加工して準備するのは大抵のTypescript処理で行っていると思いますが、これをReactのコンポーネント内で行ってしまうと無駄なレンダリングの温床となってしまいます。
function App() {
const [config, setConfig] = useState() //初回データ取得用のstate
useEffect(() => {
//ここでデータの取得・加工を実行
setState(//取得したデータを設定)
}, [])
return (
<>
...コンポーネント
</>
)
}
export default App
確かにuseEffectの第二変数に空配列[]を渡すことで一回のみのonMounted的な処理が可能ですが、コンポーネントの状態によってはデータが取得できない可能性もありますし、
何よりデータ取得の実行タイミングを開発者側でコントロール出来ないという致命的な欠陥があり、予期せぬ不具合の原因にもなりかねません。
Reactをマウントする直前のTypescript処理内で必要なデータを取得・加工してからコンポーネントに渡す流れの方がいいでしょう。
import App from './App.tsx'
const initConfig = async() => {
//ここでデータ取得を実行
}
initConfig().then(() => {
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App config={//取得・加工したデータ}/>
</StrictMode>,
)
})
function App(props:{config:***}) {//渡されたデータをpropsで受け取る
const [config, setConfig] = useState(props.config)//stateの初期値
useEffect(() => {
}, [])
return (
<>
...コンポーネント
</>
)
}
export default App
propsで加工したデータを受け取ってからuseStateの初期値とすることでデータの堅牢性を高めつつ柔軟な運用が可能になります。
2. レンダリングを伴うhooksの更新は必要でない限り極力避ける。
Reactを学習する上でまず多用するのがuseStateのhooksだと思いますが、VueのrefやSvelteの$stateと違い画面描画で使用されていなくても値が変化するだけで画面が再レンダリングされるという副作用があります。
条件分岐用のフラグとして使用したり変更監視用の反復処理の中で繰り返し更新を掛けてしまうと画面の負荷が極端に重くなってしまいます。
const [value, setValue] = useState(props.config)//stateの初期値
const updateValue = (newValue) => {//この関数が呼び出される度に画面が再レンダリングされる
setValue(newValue)
}
useState変数を更新する箇所を極力限定し、更新の際にも「本当に変更の必要はあるのか」を判断させるように処理を実装することで画面の再レンダリングを極力抑えて画面のパフォーマンスを向上させることが出来ます。
const [value, setValue] = useState(props.config)//stateの初期値
const updateValue = (newValue) => {//この関数が呼び出される度に画面が再レンダリングされる
if(value !== newValue){
setValue(newValue) //値が違う場合のみ更新する。
}
}
3. 画面描画に関係ない、けど永続化したい変数にはuseRefを使おう
ReactにはuseRefというhookもあります。useStateと同じように画面がレンダリングされても保持される変数を作成できるのですが、useStateと違い変更されても画面が再レンダリングされません。
再レンダリングを起こさないため画面上の表示データには残念ながら使用できませんが、内部の条件分岐に使うフラグやRESTのエンドポイントなどstateが参照する変数として利用できます。
コンポーネントで1回だけ実行したい処理がある場合はuseEffectの第二引数に空配列[]を渡すことで実現できますが、開発環境では何故か2回実行されるという変な仕様があります。
useRefを使って1回だけの処理にフラグを実装することで必ず一回だけ実行される処理を作ることも出来ます。
stateと違いフラグの変更時に画面は再レンダリングされません。
const initFlag = useRef(false)
const [config setConfig] = useState()
useEffect(() => {
if(!initFlag.current){
//configの初期化処理
initFlag.current = true
}
}, [config])
まとめ
Vueと違って無駄なレンダリングを起こしやすいReactですが、hooksの仕様を正しく把握してReactを本来のUIライブラリとしての役目に専念させることで簡単に動作の軽いWEBアプリを実現できます。
正しくReactを理解して楽しくUIを作っていきましょう!