お断りと本記事のナラティブ
3年前くらいからReactについて耳にしていたと思う。
1年前に漸く身を乗り出しReactに向き合ってみようと調べだし、
Qiita記事にも触れるようになった。
仕事でも、一時期全然分かっていない状態でReactのWebアプリケーションの改修をさせてもらう機会に2,3ヵ月程恵まれ、雰囲気を味わった程度。。。
そこでもQiitaの記事等を見たけど、正直処理の詳細は書かれているもののイメージが湧かず、あまり頭には入ってこなかった。
この記事を書いたのは、そんな当時の自分にとって取っ掛かりとして踏まえておいて欲しい、良記事への足掛け位の位置づけが欲しかったからであり、最近少し学習してきて考えがまとまってきた気がするので、自身の現状理解をスケッチして今後の自分の備忘としての学習記録とするためである・・・
ざっくりとしたコンポーネント構成図イメージ
/**
* お決まりインポート文各種
*/
import { reactHooksA } from "react";
import hoge from './hoge.jsx';
export function Xfunction = () => {
/**
* 【レンダーロジック】
* React Hooksを必ず関数内レンダーロジックのトップレベルに配置
*/
const something = reactHooksA();
/**
* 【レンダーロジック】
* JSXに加える処理ブロック(関数や変数の定義)
*/
const someFunction = () => {};
return (
/**
* 【JSX】
* return文にて()でマークアップとなるJSXを囲う
* レンダーロジックを使用する
*/
<JSX>
)
};
押さえておく前提知識
Reactは、「state」→「コンポーネント」のイメージ
"stateが変われば再レンダリングされる"
UI = f(state)
「画面は状態を入力とする関数の出力」とよく表現される
改めて、
"stateが変われば再レンダリングされる"
→再レンダリングって?
コンポーネントを記述する関数の再実行!! ≒ UI再描画
React Hooksとレンダリング
ReactのHooksで今の段階で登場して存じ上げているHooksに、
・useState
・useEffect
・useCallback
があるが、レンダリング発火に関与しているのはuseStateのみの印象。
再レンダリングはコンポーネントの関数そのものを再実行すること。
他のHooksは再レンダリングで処理が通る時に、処理されるイメージ。
🔴調べていてレンダーロジックと言う便利なワードを見つけたのだけど、コンポーネント(関数)のreturn、JSXの上部に書かれている領域で、レンダリングする際に走る処理領域のことを指すらしい。
再レンダリングされるとは関数が再実行されるわけで、レンダーロジックからJSXまで一気通貫で処理が走ります。
尚、再レンダリングには、
1.レンダーフェーズ:関数の再実行→JSXの生成
2.差分検出:JSXの新旧比較
3.コミットフェーズ:差分を実DOMに反映
4.エフェクト実行:依存を参照してuseEffect実行
があるとのこと
React Hooks紹介メモ
useState
stateを表す変数とセッターを明記。"stateが変われば再レンダリング"
const [number, setNumber] = useState(0);
useState()内はstateの初期値。
レンダーロジック内で、上記setNumberが使用されたりして、useState左辺で指定したstate(ここではnumber)が変わったら、再レンダリングするというスイッチ的な役割を行うフック。
stateというトリガー設置的存在。
しつこいくらいに、"stateが変われば再レンダリング"
stateを変更する時は角括弧[ ]内2番目に記述したセッターを使用する。
(「セッター」という言葉を使っていますが、代入ではなく次回レンダリング時の値予約と更新のリクエストであることに留意)
const someFunction = () => setNumber(number+1);
useEffect
レンダーロジックにてレンダリングされた時に第2引数(下記では[num])に変化があった時に処理を行うようにする。第2引数は依存配列と呼ばれる。
依存配列の要素に変更があった場合に実行される。
useEffect(() => {
if (num > 0) {
if (num % 2 === 0) {
isRed || setIsRed(true); // 処理A
} else {
isRed && setIsRed(false); // 処理B
}
}
}, [num]);
これ(useEffect)が無いと、レンダーロジックだけに、レンダリングされる時に問答無用で処理が走ってしまう。
この例で何が問題かと言うと、
isRedを他の場所で切り替える際に、再レンダリングされて強制的にここを通るので※、
※isRedは暗黙的にuseStateで指定したReactのレンダリングトリガーであるstateと想定
| 再レンダリング発火時点でnumが偶数時(再レンダリング発火前trueだったと仮定) |
|---|
| isRedを他所でtrueからfalseにした→再レンダリング開始→処理Aにてtrueになる |
| →他所の変更が抹消される |
| 再レンダリング発火時点でnumが奇数時(再レンダリング発火前falseだったと仮定) |
|---|
| isRedを他所でfalseからtrueにした→再レンダリング開始→処理Bにてfalseになる |
| →他所の変更が抹消される |
というバグが起こる・・・
useCallback
取り合えず先に説明
子コンポーネントに渡すpropsに関数を与える場合、親コンポーネントの再レンダリングで、与えた関数起因の再レンダリングを起こさないようにする為に施すフック。
(※「前提知識」「前提知識 補足」編:分かっている前提で書いてしまっています。。。![]()
![]()
)
(ラッパーとかラッピングとか言おうとしたらちょっとニュアンス違うみたいです・・・「フック」です:ラッパーと言うとラッピングした関数に追加処理を加える的なニュアンスになり易いそうです。。。ex)Java SpringのAOPとか)
前提知識
レンダーロジックで定義された関数は、レンダリングされる毎に新しく作り変えられる!
(毎回レンダリングされる毎にJavaで言うところのnewされてしまう・・・的な)
だから、子コンポーネントに関数をpropsとして渡すと、子コンポーネントも再レンダリングされることになっているらしいんですね!
(※「前提知識 補足」編:propsとか分かっている前提で書いてしまっています。。。![]()
![]()
)
(ざっくり弁解としてはprops=子コンポーネントが親コンポーネントから譲り受ける引数の認識)
再レンダリング自体、コンポーネントが多層化したり、子コンポーネントの処理が重いと、出来るだけ省力化したい要望があるとのことです!
表示速度と時間コスト的にもレンダリング負荷を下げたい的な。。。
しかし、React既定の決まりとしては親コンポーネントが再レンダリングされると、子コンポーネントも再レンダリングされることになっており、子コンポーネントをmemoで包んだ場合、この既定はクリアされ、親コンポーネントが再レンダリングされたからという理由だけで子コンポーネントが再レンダリングされるということはなくなります。
ところが、propsに親コンポーネントの関数が入っていると再レンダリングされてしまいます!
1. React既定の決まりとしては親コンポーネントが再レンダリングされると、子コンポーネントも再レンダリングされる
2. 子コンポーネントは引き受けるpropsに親コンポーネントの関数があると親コンポーネントが再レンダリングされた時、再レンダリングされてしまう
こんな感じの条件になっているので、1番目の条件をmemoでラッピングして解消した上で、その先の2番目の条件をクリアする為のフックがuseCallbackという感じだそうです・・・
(memoについてはラッピングとかラッパーと呼んで良いとのこと。。。)
(ですので、親コンポーネント再レンダリング時に子コンポーネント再レンダリングを防ぐために子コンポーネントに"memoというラッパーを適用"と表現可能)
前提知識 補足
先につらつらと説明を並べてしまいましたが、useCallbackを説明するうえで、
- コンポーネントの階層(親コンポーネントと子コンポーネント)
- propsの話
- memo
- レンダリングのコスト省力化
こう言ったことについて考慮しなければならないので、本当はこの記事で丸ッと詳細を書かなければならないのだけども、それも含めて取っ掛かりになれば良いくらいの思いで止めさせて頂き、一旦ここで説明を切り上げさせていただきたく![]()
![]()
。。。
/**
* お決まりインポート文各種
*/
import { useState, useCallback } from "react";
import hoge from './hoge.jsx';
export function ParentComponent = () => {
/**
* 【レンダーロジック】
* React Hooksを関数内レンダーロジックのトップレベルに配置
*/
const [ isRed, setIsRed ] = useState(true);
/**
* 【レンダーロジック】
* JSXに加える処理ブロック(関数や変数の定義)
*/
const someFunction = useCallback(() => { return setIsRed(false) }, []);
return (
/**
* 【JSX】
* return文にて()でマークアップとなるJSXを囲う
* レンダーロジックを使用する
*/
<>
<ChildComponent someFunc={someFunction} />
</>
)
};
/**
* お決まりインポート文各種
*/
import { reactHooksA } from "react";
import hoge from './hoge.jsx';
export function ChildComponent = memo(() => {
/**
* 【レンダーロジック】
* React Hooksを関数内レンダーロジックのトップレベルに配置
*/
const something = reactHooksA();
/**
* 【レンダーロジック】
* JSXに加える処理ブロック(関数や変数の定義)
*/
const someFunction = someFunction = () => {};
return (
/**
* 【JSX】
* return文にて()でマークアップとなるJSXを囲う
* レンダーロジックを使用する
*/
<JSX>
)
});
第2引数は依存配列と呼ばれる。ここでは空。
const someFunction = useCallback(() => { return setIsRed(false) }, []);
依存配列の要素に変更があった時に実行される。
あとがき
他、本テーマを理解するうえで、
・いきなり出てくるJSXとは?
・JSXの<>って何?
・「React Hooksを関数内レンダーロジックのトップレベルに配置」とは?
・コンポーネントってなんぞ?分かっている体で話進んでるけど・・・
など、自分が読んでいても細かいところ説明が必要と感じるところは出てきますし、
Reactと言えば「Keep Components pure」と言って、コンポーネントは純粋でなければならないという主張とか、副作用についての取り扱い方の概念とかあるらしいですが、トピックは様々にあるものの、
一旦これにてお開きとさせていただきます![]()
以上、
当時の自分に話すなら、
兎に角、色々React Hooksってあるけれど、
Reactってstateの変化がUI描画のトリガーになっていて、そのstateを扱うHookがuseStateだった。
UI描画はファンクションベースのコンポーネントと言う関数単位で管理されていて、描画は関数の実行によってマークアップに影響を及ぼすDOMという領域で行われ、Reactに関して仮想DOMとかあるんだけども、兎に角描画にまつわるそれら一連のことをレンダリングと言うんだって
フックって言葉よく見かけるけど、React HooksのHookのことで、ラッパーとは違って、仕掛けみたいな感じで捉えとくと良いんじゃないかな
useState以外にもuseなんちゃらって出てくるけど、やっぱり使用する際のシナリオで押さえておくと色んな記事の説明も分かってくると思うよ
とは言え、シナリオ・ユースケースが読み解きづらいのならば、React特有のコンポーネント管理の世界にあると思うので記法に慣れる必要はあると思うな
コンポーネントと言えば関数で書かれていて、state起点の再レンダリングを軸とした世界で、再レンダリングは、関数が再実行されるという意味だからレンダーロジックもJSXも一気通貫で処理が流れるからね!
って話しかけると思います!!
