Skip to content

Commit

Permalink
Add: React learning
Browse files Browse the repository at this point in the history
  • Loading branch information
Hayao0819 committed Jun 18, 2024
1 parent 6e15201 commit 8b9b316
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 1 deletion.
164 changes: 164 additions & 0 deletions posts/20240618/react/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
title: Reactのライフサイクルと最適化についてまとめる
description: ""
date: 2024-06-18T12:05:44.371936+09:00
categories:
- 技術系
draft: false
publish: true
---

Reactが再レンダリングされる条件とパフォーマンスの改善について、基礎から自分の復習を込めてまとめてみる。

ちなみに最近フロントエンドに飽きたので間違っていても責任は取りません。

## 関数型プログラミングとReact

関数型プログラミングはある値に対して特定の関数を渡すことで値を加工することでプログラムを記述していく言語。

しっかりと書いたことはないのであまり深い言及はできないのだが、関数を組み合わせていくものだと考えていい。

ここで重要なのは関数型プログラミングそのものではなく、その中で出てくる副作用の概念である。

以下に関数を定義する。

```ts
const sum = (a: number, b: number) => a + b;
```

この関数は同じ引数を渡した場合常に同じ結果が返ってくる。これは自明ではあるが引数の値を足し算する以外の処理が書かれていないためである。

更に、変数aとbのアドレスそのものを変更するわけでもないためそれらの値を変更することもない。

このような外部への作用が一切無く、常に同じ引数において同じ結果を返す関数を純粋関数という。

関数において同じ結果が返ってくることを **冪等性(idempotence)がある** と表現する。

Reactの文脈においては、以下のようなコンポーネントは純粋であると言える。

```tsx
const Hello = () => <>Hello World</>
```

一方で、以下のようなコードは常に同じ結果が返ってくるとは限らない。

```ts
let hoge = 0;
const count = (add: number) => {
hoge += add;
return hoge
}
```

この関数は1回目の実行と2回目の実行で返ってくる値が異なる。これは関数において`hoge`というグローバル変数を書き換えているためである。

このような関数の外部に影響を及びことを **副作用(side effect)** と呼ぶ。

これは医学的な「予期せぬ作用」という意味合いではない。「引数に対して値を返す」 **主作用** であり、それ以外が副作用となる。

## Reactにおける関数コンポーネントのライフサイクル

※クラスコンポーネントはここでは扱わない。

対象の関数コンポーネントが呼ばれたとき、その関数内でフックの計算が行われ最終的にレンダリングされるjsxが決定される。

jsxはHTMLではなくその実態はただのJavaScriptの関数の呼び出しである。

(言い換えればその気になればjsxを用いてReactの代わりとなるライブラリを作ることも可能である。そしてその実例がSolid.jsやPreactである。すなわちjsxはReactの独自機能ではなく、ECMAScriptにおける一種の特殊なSyntax Sugerである。)

レンダリングにおいては仮想DOMを用いて比較が行われ、実際のDOMとの差分が計算、DOMとして描画される。

(多分な語弊と誤解を恐れずにあえて大胆に表現すると、この差分計算等を事前に行ってしまおうというのがSSGであり、サーバ側で行ってしまおうというのがSSRであり、それが可能なのがRSCである。)

SSRが近年流行したり、Solid.jsのようなものが出現するのはこの仮想DOMの比較計算はそれなりの計算量があり軽量ではないという意見のもとである。

[【イラストで分かる】Reactとライフサイクル](https://zenn.dev/koya_tech/articles/16d8b11b5062bd)

## レンダリングが行われるタイミング

Reactではコンポーネントの値が変更されると、その変更をDOMに適用するために再レンダリングが行われる。

コンポーネントの値というものについて具体的に言及すれば、関数コンポーネントにおいてはまさにHooksである。

またコンポーネント自身が再レンダリングされたとき、そのコンポーネントが呼び出している子のコンポーネントも再レンダリングされる。

以下に具体例を示す。

```tsx
const Header = () => <p>ボタンをクリックすると数字が増えるよ!</p>

const Counter = () => {
const [count, setCount] = useState<number>(0);
const incrementCount = () => setCount(count + 1);
return (
<div>
<Header />
<p>{count}</p>
<button onClick={incrementCount}>Click me!</button>
</div>
);
};
```

Counterコンポーネントにおいて、ボタンがクリックされる(`incrementCount`によって関数内の値が更新される)と同時にコンポーネント全体で再レンダリングが実行される。コンポーネントが再レンダリングされると、そのコンポーネントが呼び出してる子コンポーネントも再レンダリングされるため、`Header`も再レンダリングの対象である。

ここで注目して欲しいのは、`Counter`は実行されるたびに値が変わるため副作用が存在するのに対し、Headerは純粋関数である。

すなわち、Headerは何度実行されても返り値が変化することはないのである。ここで思い出してほしいのはレンダリングはそれなりの計算量を伴うということである。

(再レンダリングされる必要のない)純粋なコンポーネントに対しても再レンダリングが実行されてしまうのは、無駄な計算ではないだろうか?

この無駄を省くのがReactの最適化であり、パフォーマンスの改善である。(後述する)

## 再レンダリングを観測する方法

再レンダリングはレンダリングされた結果であるブラウザのDOMだけでは観測することはできない。(純粋関数であるため結果だけ見ても変化を観測できないのは当然である。)

そこで、Reactにおいてレンダリングを確認する方法をいくつか紹介する。

### console.logを用いる

関数コンポーネントのレンダリングは、レンダリングされるたびにその関数が実行されることで行われる。

これを用いると、関数にconsole.logを仕込むだけでそのレンダリングを垣間見ることができる。

(console.logによるログの出力は関数の返り値以外への影響のため一種の副作用である。)

```tsx
const Header = () => {
console.log("rendering Header")
return <p>ボタンをクリックすると数字が増えるよ!</p>
}
```

[こちら](/playground/learn-react/rendering/no-memo/)にこのコンポーネントをデプロイしてある。

開発者ツールを開くと、ボタンをクリックするたびにconsoleに文字が出力されているのが確認できるはずである。

## Reactの開発者ツールを用いる

![React 開発の拡張機能のスクリーンショット](react-devtool.png)

React公式が開発しているブラウザ拡張機能で、レンダリングの回数とそれにかかった時間を見ることができる。

拡張機能のインストールは[こちら](https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)からインストール。

使い方についてはいろんなサイトが引っかかるので適当に参考にすること。

[React Developer Toolsのすすめ \#redux \- Qiita](https://qiita.com/sh-suzuki0301/items/9c2af4b28ba665cc0744)

## 再レンダリングされる厳密な条件

TODO

## メモ化

TODO

## useEffectの使い方について

TODO

## 終わりに

TODO
Binary file added public/posts/20240618/react/react-devtool.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions src/app/playground/learn-react/rendering/no-memo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client";

import { useState } from "react";

const Header = () => {
console.log("Header rendered!");
return <p>ボタンをクリックすると数字が増えるよ!</p>;
};

const Counter = () => {
const [count, setCount] = useState<number>(0);
const incrementCount = () => setCount(count + 1);
return (
<div>
<Header />
<p>{count}</p>
<button onClick={incrementCount}>Click me!</button>
</div>
);
};

export { Counter as default };
4 changes: 3 additions & 1 deletion src/components/elements/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "@/style/markdown.css";

import { MDXComponents } from "mdx/types";
import { MDXRemote } from "next-mdx-remote/rsc";
import { Link } from "next-view-transitions";
Expand Down Expand Up @@ -53,7 +55,7 @@ export default async function Markdown({ content, basepath }: { content: string;
}
props = { ...props, src };

return <img {...props} className="p-4" />;
return <img {...props} className="py-4" />;
},
code: ({ children }) => <code className="text-sky-400">{children}</code>,

Expand Down
4 changes: 4 additions & 0 deletions src/style/markdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
figure[data-rehype-pretty-code-figure] > pre {
padding: 10px;
overflow: scroll;
}

0 comments on commit 8b9b316

Please sign in to comment.