🍈

stateを腹落ちするためには”レンダリング”を理解らなきゃダメらしい #2

に公開2

📢 このシリーズについて

このシリーズは、React公式ドキュメント( https://ja.react.dev/ )を(できるだけ)全て読み、学んだことやコード例、自分なりの補足をまとめていくチャレンジの記録です。

目的

  • Reactの基礎〜応用を公式の正しい情報で体系的に理解する
  • 自分の言葉でアウトプットして知識を定着させる

内容

このシリーズでは、ただ内容をなぞるのではなく、「自分がReactを使っていても気づかなかった事実や落とし穴」を重点的にまとめます。

読んだときに「へぇ、そうなんだ!」となったポイントは必ず記録します!

目次 - 本記事で取り扱う内容


🗂 基本情報



📌 内容

この記事は “ hooks “ という概念の導入的な内容が書かれている。

state とは

Reactにおいて、時間の経過とともに変化するデータを「state」と定義される。別の言い方をすると、コンポーネントが持つ、固有のメモリのことをstateという。

ユーザー操作などで値が変わると、React は 次のレンダー を予約しUI を更新。

UI視点:入力 → 表示更新の流れ

  1. ユーザーの操作で イベントハンドラ が発火
  2. ハンドラ内で setState を呼ぶ(= 次のレンダーの予約
  3. ハンドラが終わったあと、React が再レンダー → 変更が UI に反映

“state はスナップショットである ” がムズイ

本文中にこんな記述があった。

通常の JavaScript の変数とは異なり、React の state はスナップショットのような動作をします。セットしても既存の state 変数は変更されず、代わりに再レンダーがトリガされます。初見ではびっくりするかもしれません。

console.log(count);  // 0
setCount(count + 1); // Request a re-render with 1
console.log(count);  // Still 0!

理解自体は難しくないが、なんだか腹落ちしない。”関数の発火”から”UIの更新(DOMの更新)”の流れがあんまわかってないことが原因な気がする。

調べると、こんな流れで動いているようだ。

[ユーザークリック]     ← イベント発火
        │
        ▼
  onClick 関数実行  ────────────────────────────────┐
        │  console.log(count) // 今の値(例: 0)setCount(count + 1) // 次のレンダー用に予約console.log(count) // まだ変わらない (0)
        └───────────── イベント修了 ────────────────┘
        │
        ▼
【React内部】
 1. 状態更新予約を確認
		 -> React内部の Update Queue(更新キュー) に
			 「どのコンポーネントのstateをどう変えるか」という情報を積む
 2. 「次のレンダーを実行せよ」とスケジュール。(優先度付きキューみたいなもん)
        │
        ▼
[レンダーフェーズ]
  ・ 新しいstateを使ってコンポーネント実行
  ・ JSX計算 → 仮想DOM作成
  ・ 前回仮想DOMとの差分計算
        │
        ▼
[コミットフェーズ]
  ・ 実DOMをまとめて更新
  ・ useLayoutEffect → useEffect 実行

つまり、stateの変更は、それを含む関数すべてが終わるまで反映されないということみたい。

[ state(X)を変更する処理を含む関数 ]内で X を呼び出したとて、再レンダリングされるのはイベント終了後であるため反映されないということかな。

意図した表示をさせるためには、どの処理がいつ行われるかを知っておくべきですね。

“一連の state の更新をキューに入れる” 前提を理解していないと沼かも

この項目では、以下のコードとUIについて言及されていた。

// コード
import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}

// UI

image.png

これの “ increment(); “ の部分が論点。

increment関数は、setScore(score + 1)が実行されているが、このコードはうまく動かない。

+3を押しても+1にしかならない。

これは+3 のonClickイベントが終了するまで score の値は変わらないことが原因。簡単にに言うと、score ( = 0 ) + 1を複数回やっても結局 0+ 1をしているだけってことみたい。

修正方法としては、increment関数を

  function increment() {
    setScore(s => s + 1);
  }

とする方法などがあるようだ。

これは”関数”を記述することで”関数”をそのままキューに蓄えることができ、レンダリングを行う際に”関数”を実行できるため想定通りの処理ができるというわけだ。

state 内のオブジェクト

stateは、いわば値の書き換え、更新を行う。

オブジェクトの場合、オブジェクトを直接書き換えるのではなく、新しいオブジェクトを生成し置き換えるという処理を行う。

その際に、目的変数以外は “ … “ というスプレッド構文を用いるとよい。これは、その他すべてを表すような構文。

“ Immer “ ってライブラリでもいい感じにできるらしい。



🚀 宣伝コーナー

個人開発もやっています🍃

ぜひ一度触ってみてください!

1. CAN I DO THIS

📜概要

🌟 「この問題、私に解ける?」がすぐ分かる! 🌟

CAN I DO THIS は、問題(文章/画像)をポンっと入れるだけで、「解くのに必要な前提知識」を瞬時に可視化するツールです💡

🛡 前提知識がないまま無理に挑んで、時間と体力をムダに消耗…

そんな経験、もうしなくてOK。

必要な知識が足りているか、すぐ判断できます!

✨ 主な機能

  • 問題を貼るだけ → 必要知識リスト+学習ルート自動生成
  • 前提 → 派生が一目で分かる依存関係グラフ
  • 登録不要&無料(広告視聴で利用可能)

🔗 今すぐ試す

Discussion

Honey32Honey32

失礼します。

「ステートがスナップショットである」とはどういうことなのか、手前味噌ですが以下な記事を見てもらうとわかると思います。

タイミングの問題ではなく、JS の構文レベルで「ステートが古い世界」と「再レンダリング後の世界」を分離しているのです。

https://qiita.com/honey32/items/ee8d1577e68b0d58678d