当サイトは、アフィリエイト広告を利用しています
useStateとuseEffectの処理実行順をまとめる。
useStateを使ったサンプルを作って実行順を確認してみる
react,emotionでタグを選択するサンプルを実装し、
useStateの実行順を確認する
reactでemotionを使えるよう必要なパッケージを入れる
{"name": "react","version": "1.0.0","description": "React example starter project","keywords": ["react","starter"],"main": "src/index.js","dependencies": {"@emotion/core": "11.0.0","@emotion/react": "11.8.2","@emotion/styled": "11.8.1","react": "18.0.0","react-dom": "18.0.0","react-scripts": "4.0.0"},"devDependencies": {"@babel/runtime": "7.13.8","typescript": "4.1.3"},"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test --env=jsdom","eject": "react-scripts eject"},"browserslist": [">0.2%","not dead","not ie <= 11","not op_mini all"]}
import { createRoot } from "react-dom/client";import App from "./App";const rootElement = document.getElementById("root");const root = createRoot(rootElement);root.render(<App />);
タグ一覧を表示し、選択するとタグの色が変わるコンポーネントを実装。
要所要所にログを仕込む
/** @jsxImportSource @emotion/react */import { css } from "@emotion/react";import { useState } from "react";export default function App() {console.log("【関数コンポーネント実行】")// タグ初期値const array = Array.of("java","javascript","gatsby",);// 選択中タグのstateを定義const [selectedTag, selectTag] = useState([]);console.log("【useStateを実行】")console.log(" ・選択中のタグ:",selectedTag)// タグ選択処理(state更新)const push = (e) => {e.preventDefault();selectTag(() => [e.target.id]);console.log("【state更新(タグ選択)】")console.log(" ・選択中のタグ:",selectedTag)};return (<div className="App">{console.log("【render(画面描画)】")}<h2>タグ</h2><div css={[tagsGrid]}>{array.map((val, i) => (<buttonkey={i}id={val}css={[grigItem(selectedTag, val,i)]}onClick={(e) => push(e)}>{val}</button>))}</div>{console.log("---------------------------------")}</div>);}// タグ選択時のスタイル変更const grigItem = (selectTag, select,i) => {const count = selectTag.filter((selected) => select === selected);console.log(` ${i}.emotion関数実行(タグのスタイル設定):${count}`)if (count.length !== 0) {return [css`background-color: skyblue;`];}};const tagsGrid = () => [css`display: grid;grid-template-columns: repeat(3, minmax(100px, 10%));grid-auto-rows: 50px;justify-content: center;align-items: center;gap: 10px;border: 1px solid black;margin: auto;`];
作成したサンプルを初期表示から順に実行して、ログを確認する
【関数コンポーネント実行】【useStateを実行】・選択中のタグ: []【render(画面描画)】0.emotion関数実行(タグのスタイル設定):1.emotion関数実行(タグのスタイル設定):2.emotion関数実行(タグのスタイル設定):---------------------------------
初期表示時は特に特別なことはなく
と上から順に実行される
// 【関数コンポーネント実行】// 【useStateを実行】// ・選択中のタグ: []// 【render(画面描画)】// 0.emotion関数実行(タグのスタイル設定):// 1.emotion関数実行(タグのスタイル設定):// 2.emotion関数実行(タグのスタイル設定):// ---------------------------------// ↑は初期表示時のログ【state更新(タグ選択)】・選択中のタグ: [] //選択したタグ"java"はここではstateに未反映【関数コンポーネント実行】【useStateを実行】・選択中のタグ: (1) ["java"] //選択したタグ"java"はここでstateに反映される【render(画面描画)】0.emotion関数実行(タグのスタイル設定):java1.emotion関数実行(タグのスタイル設定):2.emotion関数実行(タグのスタイル設定):---------------------------------
初期表示からログを選択した時の動作を見てみる。
このログからuseStateの値が完全に更新されるのは
setStateでstateを更新された後、関数コンポーネントとuseStateが再度実行された後であることが
わかる。
その後に更新されたstateを使ってて画面を再描画している。
useState,useEffectを使ったサンプルを作って実行順を確認してみる
useStateとuseEffectを使って 単純なカウンター実装する
import { createRoot } from "react-dom/client";import App from "./App";const rootElement = document.getElementById("root");const root = createRoot(rootElement);root.render(<App />);
カウントするだけのコンポーネントにログを仕込む
import "./styles.css";import React, { useEffect, useState } from "react";export default function App() {console.log("【関数コンポーネントを実行】")//カウンターのstateconst [count, setCount] = useState(1);console.log("【useStateを実行】")console.log(" ・カウント:",count)useEffect(() => {// 任意処理console.log("【useEffect任意処理】")// クリーンアップ処理return () => {console.log("【useEffectクリーンアップ処理】")};}, [count]);// カウントアップ関数const countUp = () => {setCount((prevCount) => prevCount + 1);console.log("【state更新(カウントアップ)】")console.log(" ・カウント:",count)};return (<div className="App">{console.log("【render(画面描画)】")}<div><h2>カウンター</h2><button onClick={countUp}>click me</button>{count}</div></div>);}
作成したサンプルを初期表示から順に実行して、ログを確認する
【関数コンポーネントを実行】【useStateを実行】・カウント: 1【render(画面描画)】【useEffect任意処理】
画面レンダー後にuseEffect処理が実行される
// 【関数コンポーネントを実行】// 【useStateを実行】// ・カウント: 1// 【render(画面描画)】// 【useEffect任意処理】// ↑は初期表示時のログ【state更新(カウントアップ)】・カウント: 1 //ここでstateの値は更新されるが未反映【関数コンポーネントを実行】【useStateを実行】・カウント: 2 //ここでstateの値が2に反映される【render(画面描画)】【useEffectクリーンアップ処理】【useEffect任意処理】
初期表示からカウントアップした時の動作を見てみる。
useEffectは画面描画後に動き、さらにuseEffectのクリーンアップ処理は
任意処理よりも先に動くことがわかる。
useStateとuseEffectを使って
カウンターとカウンターログを表示するサンプルを実装する
※useEffect内でuseStateを更新するパターン
import { createRoot } from "react-dom/client";import App from "./App";const rootElement = document.getElementById("root");const root = createRoot(rootElement);root.render(<App />);
カウンターボタンとカウント数、カウンターログを表示するコンポーネントを実装し、
ログを仕込む
import "./styles.css";import React, { useEffect, useState } from "react";export default function App() {console.log("【関数コンポーネントを実行】")//カウンターのstateconst [count, setCount] = useState(1);//ログーのstateconst [log, setLog] = useState([]);console.log("【useStateを実行】")console.log(" ・カウント:",count)console.log(" ・ログ:",log)// useEffectでcountに変更が合った場合のみlog出力する.useEffect(() => {// ログを表示するsetLog((prevLog) => [...prevLog, `count${count}`]);console.log("【useEffect任意処理】")console.log(" ・ログ:",log)// クリーンアップ処理// countに変更があった場合に実行されるreturn () => {// ログが4行になったら初期化setLog((prevLog) =>prevLog.length === 4 ? [] : prevLog.map((log) => log));// カウントが4になったら初期化setCount((prevCount) => (prevCount === 4 ? 1 : prevCount));console.log("【useEffectクリーンアップ処理】")};}, [count]);// カウントアップ関数const countUp = () => {setCount((prevCount) => prevCount + 1);};return (<div className="App">{console.log("【render(画面描画)】")}<div><h2>カウンター</h2><button onClick={countUp}>click me</button>{count}</div><div><h2>log</h2>{log.map((log) => (<div key={log}>{log}</div>))}</div></div>);}
作成したサンプルを初期表示から順に実行して、ログを確認する
【関数コンポーネントを実行】【useStateを実行】・カウント: 1・ログ: []【render(画面描画)】【useEffect任意処理】・ログ: [] // stateのlogを更新するがここでは未反映、stateを更新したため再レンダリングが動く【関数コンポーネントを実行】【useStateを実行】・カウント: 1・ログ: (1) ["count1"] // stateのlogをここでは反映される【render(画面描画)】
初期表示時は下記のような動きになる
useEffectが動くのは一度画面描写された後になる。
またuseStateの時と同じでuseEffect内で更新されたstateはこのタイミングでは反映されず
次のuseState実行で反映されることがわかる。
※クリーンアップ処理は初期表示時は実行されない
// 【関数コンポーネントを実行】// 【useStateを実行】// ・カウント: 1// ・ログ: []// 【render(画面描画)】// 【useEffect任意処理】// ・ログ: []// 【関数コンポーネントを実行】// 【useStateを実行】// ・カウント: 1// ・ログ: (1) ["count1"]// 【render(画面描画)】// ↑は初期表示時のログ【state更新(カウントアップ)】・カウント: 1 // stateのcountを更新するがここでは未反映【関数コンポーネントを実行】【useStateを実行】・カウント: 2 // stateのcountの更新がここで反映される・ログ: (1) ["count1"]【render(画面描画)】【useEffectクリーンアップ処理】【useEffect任意処理】・ログ: (1) ["count1"] // stateのlogを更新するがここでは未反映、stateを更新したため再レンダリングが動く【関数コンポーネントを実行】【useStateを実行】・カウント: 2・ログ: (2) ["count1", "count2"] // stateのlogの更新がここで反映される【render(画面描画)】
初期表示からカウントアップした時の動作を見てみる。
大分、ややこしくなってしまったが要点としては
stateを更新した値が完全に反映されるのは次のuseStateかuseEffectのタイミングになる。
ということ。
要点としては下記になると思います
下記を参考にさせて頂きました
Reactでステートの値が反映されない時に試すこととその理由