React のドキュメントを読む② - インタラクティビティの追加
イベントへの応答
イベントハンドラは副作用を持っていても構わない?
答えは Yes! 。
レンダー関数とは異なり、イベントハンドラは純関数 (pure function) である必要はないので、何かを変更する(入力値をタイプに応じて変更する、ボタンの押下に応じてリストを変更する、など)のに最適な場所である。
ただし、情報を変更するためには、まずそれを格納する方法が必要で、React では、これは stateを使用して行う。
イベント伝播
コンポーネントでボタンをレンダーしているとする。
そのボタンを押下してハンドラを発火させた時、コンポーネントを使用している親コンポーネントのハンドラも発火してしまうことがある。
以下の例では、PlayMovieを押下した時、Playing! の後に You clicked on the toolbar! が表示される。
伝播を防ぐにはe.stopPropagation()
を使用する。
伝播する例
export default function Toolbar() {
return (
<div onClick={() => {
alert('You clicked on the toolbar!');
}}>
<button onClick={() => alert('Playing!')}>
Play Movie
</button>
</div>
);
}
伝播を防ぐ例
export default function Toolbar() {
return (
<div onClick={() => {
alert('You clicked on the toolbar!');
}}>
<button onClick={(e) =>
{
e.stopPropagation(); // here!
alert('Playing!');
}>
Play Movie
</button>
</div>
);
}
state:コンポーネントのメモリ
はじめてのフック
React では、useState やその他の use で始まる関数はフック (Hook) と呼ばれる。
フックは、React がレンダーされている間のみ利用可能な特別な関数。
レンダーとコミット
いつ、なぜ React はコンポーネントをレンダーするのか
React は以下のステップでレンダーする。
- レンダーのトリガ(お客様の注文を厨房に伝える)
- コンポーネントのレンダー(厨房で注文の品を料理する)
- DOM へのコミット(テーブルに注文の品を提供する)
ステップ 1:レンダーのトリガ
コンポーネントがレンダーされる理由には 2 つある。
- コンポーネントの初回レンダー
- コンポーネント(またはその祖先のいずれか)のstateの更新
アプリが開始するときには、初回のレンダーをトリガする必要がある。
import Image from './Image.js';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'))
root.render(<Image />);
また、コンポーネントが最初にレンダーされた後、set 関数を使って state を更新することで、さらなるレンダーをトリガすることができる。
ステップ 2:React がコンポーネントをレンダー
- 初回レンダー時、React はルート (root) コンポーネントを呼び出す
- 次回以降のレンダーでは、state の更新によってレンダーがトリガされた関数コンポーネントを、React が呼び出す
再レンダー時には、React は前回のレンダーからどの部分が変わったのか、あるいは変わらなかったのかを計算する。
ステップ 3:React が DOM への変更をコミットする
コンポーネントをレンダー(関数として呼び出し)した後、React は DOM を変更する。
- 初回レンダー時には、React は
appendChild()
DOM API を使用して、作成したすべての DOM ノードを画面に表示する。 - 再レンダー時には、React は最新のレンダー出力に合わせて DOM を変更するため、必要な最小限の操作(レンダー中に計算されたもの!)を適用する
React はレンダー間で違いがあった場合にのみ DOM ノードを変更する。
state 内のオブジェクトの更新
state を読み取り専用として扱う
state として格納するすべての JavaScript オブジェクトは読み取り専用として扱う必要がある。
機能しない(NG)例
const [position, setPosition] = useState({ x: 0, y: 0 });
position.x = 5;
機能する例
const [position, setPosition] = useState({ x: 0, y: 0 });
const update = () => {
setPosition({ x: 5, y: 0 });
}
state 内の配列の更新
配列を書き換えずに更新する
slice と splice は名前が似ているものの、非常に異なるもの。
- slice は配列や配列の一部をコピーする
- splice は(要素の挿入や削除という)配列のミューテーションを行う
React では、state 内のオブジェクトや配列を書き換えたくないため、slice(p なし!)の方をより頻繁に使用する。