Title
+高度な機能を備えた素晴らしいカウンタ
+{count}
+ +diff --git a/.eslintrc.json b/.eslintrc.json index 692d937..9a880f8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -32,7 +32,8 @@ "tailwindcss/no-custom-classname": "warn", "no-unsanitized/method": "warn", "no-unsanitized/property": "warn", - "react/prop-types": "off" + "react/prop-types": "off", + "react/display-name": "off" }, "overrides": [ { diff --git a/posts/20240618/react/index.md b/posts/20240618/react/index.md index 913a274..71913dd 100644 --- a/posts/20240618/react/index.md +++ b/posts/20240618/react/index.md @@ -131,11 +131,11 @@ const Header = () => { } ``` -[こちら](/playground/learn-react/rendering/no-memo/)にこのコンポーネントをデプロイしてある。 +[こちら](/playground/learn-react/rendering/0-simple-counter/)にこのコンポーネントをデプロイしてある。 開発者ツールを開くと、ボタンをクリックするたびにconsoleに文字が出力されているのが確認できるはずである。 -## Reactの開発者ツールを用いる +### Reactの開発者ツールを用いる ![React 開発の拡張機能のスクリーンショット](react-devtool.png) @@ -147,11 +147,182 @@ React公式が開発しているブラウザ拡張機能で、レンダリング [React Developer Toolsのすすめ \#redux \- Qiita](https://qiita.com/sh-suzuki0301/items/9c2af4b28ba665cc0744) -## 再レンダリングされる厳密な条件 +## 再レンダリングされる厳密な条件と、その回避方法 + +### 再レンダリングが起こる仕組み + +再レンダリングは結局のところ以下の場合において行われる + +- stateやContextをはじめとした値の変化 +- 親コンポーネントの再レンダリング +- カスタムフックの変化 + +Reactの再レンダリングについての記事でよく見かけるのはpropsの変化であるがこれは間違いである(詳細は[こちら](https://qiita.com/yokoto/items/ee3ed0b3ca905b9016d3#%EF%B8%8F-%E5%86%8D%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AE%E7%90%86%E7%94%B1-props-%E3%81%AE%E5%A4%89%E6%9B%B4-%E5%A4%A7%E3%81%8D%E3%81%AA%E9%96%93%E9%81%95%E3%81%84))。 + +カスタムフックの変化は、結局のところstateやContextの変化にたどり着く。 + +```tsx +const useCount = () => { + const [count, setCount] = useState(0) + const incrementCount = () => setCount(count + 1); + return [count, increment] +} +``` + +このカスタムフックにおいては、`increment`が呼び出されることでcountが変化し、それによって`useCount`そのものが更新される。 + +こうした場合にもフックの変化と検知され、レンダリングが実行される。 + +また、レンダリングは当然のことながらコンポーネント単位で行われる。言い換えれば巨大なコンポーネントにおいてはたとえごく一部の変更で済む場合においても全体の再レンダリングが行われてしまうのである。 + +すなわち、stateの変化が行われるコンポーネントを別の場所に切り出すことで影響を少なくすることができる。 + +[React再レンダリングガイド: 一度に全て理解する \#Next\.js \- Qiita](https://qiita.com/yokoto/items/ee3ed0b3ca905b9016d3) + +### 具体例と回避方法 + +```tsx +const Header = () => { + console.log("Header rendered!"); + return
Myheader
; +}; + +const MyApp = () => { + const [count, setCount] = useState(0) + const incrementCount = () => setCount(count + 1); + + return <> +高度な機能を備えた素晴らしいカウンタ
+{count}
+ +{count}
+ + > + ); +}; + +const MyApp = () => { + return ( + <> +高度な機能を備えた素晴らしいカウンタ
+Myheader
; +}; +``` + +この状態では親のコンポーネントがレンダリングされる度に、それに倣ってHeaderも再レンダリングされてしまう。 + +これに`memo`という関数を挟んでみる。 + +```tsx +const Header = memo(()=>{ + console.log("Header rendered!"); + returnMyHeader
+}) +``` + +こうすることでHeaderコンポーネント全体がメモ化される。memo関数は引数に渡したコンポーネントをそのまま返すため、返り値を通常のコンポーネントと同じように利用できる。 + +唯一の違いは「Propsに変化が無い場合は再レンダリングしない」という点である。 + +[公式ドキュメント](https://ja.react.dev/reference/react/memo)を参照すればわかることではあるが、`memo`はデフォルトの場合において個々のPropsを`Object.is`を用いて比較する。 + +比較した結果前回のPropsに変化がなければ、計算済みのコンポーネントをそのまま返すというわけだ。 + +この結果については以下で確認できる。 + +[/playground/learn-react/rendering/3-memo](/playground/learn-react/rendering/3-memo) + +### useCallback TODO -## メモ化 +### useMemo TODO @@ -161,4 +332,10 @@ TODO ## 終わりに -TODO +ここまで基本的なReactのパフォーマンスの改善の方法について紹介した。 + +しかし、Nextをフレームワークとして用いる場合にはReact 19の最新機能を使ったり、Next側のキャッシュ機構を用いたり、更にはSSRをフル活用することで更にパフォーマンスを改善することができる。 + +また、JotaiやRecailのような状態管理ライブラリを用いる場合にもそれぞれに即したパフォーマンスの改善方法が存在する。 + +ハヤオの技術力不足もありそれらすべてを網羅することはできないが、まずはこれらの基本的なパフォーマンス改善を行ってみてほしい。 diff --git a/src/app/(hayao)/blog/layout.tsx b/src/app/(hayao)/blog/layout.tsx index e55b10d..d15a83b 100644 --- a/src/app/(hayao)/blog/layout.tsx +++ b/src/app/(hayao)/blog/layout.tsx @@ -14,7 +14,7 @@ export default function BlogLayout({ children }: { children: React.ReactNode }) return (MyHader
; +}; + +const AppBar = () => { + console.log("AppBar rendered!"); + returnMyAppBar
; +}; + +const SNSLinks = () => { + console.log("SNSLinks rendered!"); + returnMySNSLinks
; +}; + +const MyApp = () => { + const [count, setCount] = useState(0); + const incrementCount = () => setCount(count + 1); + + return ( + <> +高度な機能を備えた素晴らしいカウンタ
+{count}
+ +MyHeader
; +}; + +const AppBar = () => { + console.log("AppBar rendered!"); + returnMyAppBar
; +}; + +const SNSLinks = () => { + console.log("SNSLinks rendered!"); + returnMySNSLinks
; +}; + +const Counter = () => { + const [count, setCount] = useState(0); + const incrementCount = () => setCount(count + 1); + console.log("Counter rendered!"); + return ( + <> +{count}
+ + > + ); +}; + +const MyApp = () => { + return ( + <> +高度な機能を備えた素晴らしいカウンタ
+MyHeader
; +}); + +const AppBar = memo(() => { + console.log("AppBar rendered!"); + returnMyAppBar
; +}); + +const SNSLinks = memo(() => { + console.log("SNSLinks rendered!"); + returnMySNSLinks
; +}); + +const MyApp = () => { + const [count, setCount] = useState(0); + const incrementCount = () => setCount(count + 1); + + return ( + <> +高度な機能を備えた素晴らしいカウンタ
+{count}
+ ++
{children}
), @@ -57,7 +60,9 @@ export default async function Markdown({ content, basepath }: { content: string; return ; }, - code: ({ children }) =>{children}
,
+ code: ({ children }) => {children}
,
+
+ ul: ({ children }) => {children},