復習のReact
基本的にJavaScriptで書くが、必要があればTypeScript版のコードを掲載する。
React18を対象にする
Next.jsなどのフレームワークは扱わない
Viteで環境構築する
コンポーネント
propsとstateはコンポーネントで値(状態 ≒ state)を扱う。propsは引数、stateはコンポーネントのローカル変数。
propsにはいろんな型の値を渡せる(プリミティブ & オブジェクト)。
const Child = (props) => {
console.log(props)
return (
<>
<p>id: {props.obj.id}</p>
<p>name: {props.obj.name}</p>
</>
)
}
const App = () => {
return (
<Child obj={{ id: 1, name: "kento" }} />
)
}
分割代入で受け取る。
const Child = ({ obj: { id, name }}) => {
return (
<>
<p>id: {id}</p>
<p>name: {name}</p>
</>
)
}
const App = () => {
return (
<Child obj={{ id: 1, name: "kento" }} />
)
}
TypeScriptでの型定義。
const Child = ({ obj: { id, name }}: { obj: { id: number, name: string }}) => {
return (
<>
<p>id: {id}</p>
<p>name: {name}</p>
</>
)
}
useState
const App = () => {
const [count, setCount] = useState(0)
return (
<>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
)
}
type Book = {
id: number,
name: string
}
const books = [
{ id: 1, name: "kento" },
{ id: 2, name: "kouta" },
{ id: 3, name: "nao" },
{ id: 4, name: "miku" },
]
const Books = ({ books }: { books: Book[] }) => {
return (
<>
{books.map(({id, name}) => (
<Book id={id} name={name} key={id} />
))}
</>
)
}
const Book = ({ id, name }: Book) => {
return (
<>
<p>id: {id}, name: {name}</p>
</>
)
}
const App = () => {
return (
<Books books={books} />
)
}
stateの更新は非同期
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
};
このコードでは、setCount(count + 1) を2回呼び出しています。しかし、この場合、count は関数が呼ばれた時点の値 (すなわち現在の状態) を参照しています。
具体的な流れ 最初の setCount(count + 1) が呼ばれると、React は新しい状態を「現在の count + 1」に更新するようにスケジュールします。 2回目の setCount(count + 1) も、同じ現在の count を参照して「現在の count + 1」に更新するようにスケジュールします。 React は状態の更新をバッチ処理するため、結果として count は 1 増加するだけ になります。
const handleClick = () => {
setCount((count) => count + 1);
setCount((count) => count + 1);
};
このコードでは、setCount に アップデート関数 を渡しています。この関数は「前回の状態」を引数として受け取ります。
具体的な流れ 最初の setCount((count) => count + 1) が呼ばれると、React は現在の状態 (count) を基に「count + 1」を計算し、次の状態をスケジュールします。 2回目の setCount((count) => count + 1) が呼ばれると、React は 最初の更新後の状態 を引数として「count + 1」を計算し、次の状態をスケジュールします。 結果として、count は 2 増加 します。
違いの要点 コード1: count は関数が呼ばれた時点の値を直接参照しているため、複数の setCount 呼び出しは同じ値を基に処理されます。 コード2: count はアップデート関数の引数として渡される「最新の状態」を基に処理されるため、逐次的に更新されます。 推奨方法 状態更新が前回の状態に依存する場合は、コード2のようにアップデート関数を使用することを推奨します。これは、複数回の更新や非同期処理が絡む場合にも、意図通りの動作を保証できるからです。
コンポーネントが再レンダリングされるタイミング(詳しく書く)
- stateが変化した時
- propsが変化した時
- 親コンポーネントが再レンダリングされた時
useStateの更新
setCount
を呼び出すとstateであるcount
が更新され、App
コンポーネントが再レンダリングされる。
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
return (
<>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
propsが変化した時
propsが変化した時にもレンダリングされる。stateが更新されることでAppコンポーネントが再レンダリングされ、propsが更新されることでChildコンポーネントが再レンダリングされる。
import { useState } from "react";
const Child = ({ count }: { count: number }) => {
console.log("Child Rendering");
return (
<p>{count}</p>
)
}
function App() {
console.log("App Rendering");
const [count, setCount] = useState(0);
return (
<>
<Child count={count} />
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
親コンポーネントが再レンダリングされた時
子コンポーネントのpropsは変化していないが、Appが混信されたのでChildも更新される
import { useState } from "react";
const Child = ({ count }: { count: number }) => {
console.log("Child Rendering");
return (
<p>{count}</p>
)
}
function App() {
console.log("App Rendering");
const [count, setCount] = useState(0);
return (
<>
{/* propsは更新しない */}
<Child count={1} />
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
key属性
children
ReactNodeで型定義?
参考 : Reactのchildrenの型で子コンポーネントを制御する(したかった)
レンダリング
レンダリングとは何でしょうか。恐らく「画面へのDOMの描画」という答えが一番多いと思いますが。Reactのドキュメントからもう一度おさらいしたいと思います。
ドキュメントでは、描画されるまでのステップが以下のように紹介されています。
- Triggering a render (delivering the guest’s order to the kitchen)
- Rendering the component (preparing the order in the kitchen)
- Committing to the DOM (placing the order on the table)
つまりトリガー、レンダリング、コミットという流れです。
“Rendering” is React calling your components.
とある通り、ReactにおいてレンダリングとはReactがコンポーネントを呼び出すことです。
そしてCommittingが実際にDOMを描画するという事になります。
このように、Reactにおいてはレンダリングとコミットという工程に明確に分かれていることがわかります
useState
更新用関数
useStateの更新用関数は、なぜ最新の状態を参照できるのか。
useEffect
https://www.cxr-inc.com/blog/cc98228bc2ba48d3853d077f25fb831c
https://tech.enechange.co.jp/entry/2024/06/28/100239
https://developer.mamezou-tech.com/blogs/2024/08/13/react_useeffect/
https://zenn.dev/ippe/articles/a53386986ff236
https://zenn.dev/yumemi_inc/articles/react-effect-simply-explained
https://tech.iimon.co.jp/entry/2024/07/02/152657
Transition
Suspense
自動バッチング
複数のステート更新を一回にまとめる機能。
17と18で何が変わるかの例示。
flushSync
の説明は不要。
参考
>Vueユーザー「あれ、ReactでonClickが動かない。。」のワケ - Qiita
strictモード
【React】Strictモードの挙動【バージョン18による】 #JavaScript - Qiita
useState
https://zenn.dev/counterworks/articles/putting-props-to-use-state
自動バッチング
新機能:自動バッチング React 18の新機能Automatic Batchingを理解する|デザミス株式会社 U-motion 開発チーム
Suspense
https://tech.anotherworks.co.jp/article/react-suspense-react18