shinke1987.net
雑多な備忘録等のはず。
他のカテゴリ・タブ
目次
PR

React:useLayoutEffect と useEffect の動作確認

2024-05-16 2024-05-16
カテゴリ: React

環境

React:v18.3.1

Tips

開発環境で useLayoutEffect と useEffect が2回実行されるのを防ぐには、
main.tsx の <React.StrictMode> のタグをコメントアウトすれば良い。

実行される順番を確認

再描画時に常に実行される場合

App.tsxの内容

import { ReactNode, useEffect, useLayoutEffect } from 'react';
import Parent from './Parent.tsx';

function App(): ReactNode {
  useLayoutEffect(() => {
    console.log('App useLayoutEffect');
  });

  useEffect(() => {
    console.log('App useEffect');
  });

  return (
    <>
      あいうえお
      <Parent />
    </>
  );
}

export default App;

Parent.tsxの内容

import {
  ReactNode, useEffect, useLayoutEffect, useState,
} from 'react';
import Child1 from './Child1.tsx';

function Parent(): ReactNode {
  const [child1Value, setChild1Value] = useState<string>('Child1の初期値');
  const [parentValue1, setParentValue1] = useState<string>('Parentの初期値');

  function parentBtnClickFunc() {
    alert('parentBtnClickFunc');
    setChild1Value('親からStateが変更されました');
  }

  useLayoutEffect(() => {
    console.log('Parent useLayoutEffect');
  });

  useEffect(() => {
    console.log('Parent useEffect');
  });

  return (
    <>
      <div>
        親:
        {parentValue1}
      </div>
      <button type="button" onClick={parentBtnClickFunc}>
        親ボタン
      </button>
      <Child1 childProp={child1Value} parentStateFunc={setParentValue1} />
    </>
  );
}

export default Parent;

Child1.tsxの内容

import { ReactNode, useEffect, useLayoutEffect } from 'react';

type Child1Props = {
  childProp: string,
  parentStateFunc: Function
};

function Child1({ childProp, parentStateFunc }: Child1Props): ReactNode {
  function childBtnClickFunc(): void {
    alert('childBtnClickFunc');
    parentStateFunc('子からStateが変更されました');
  }

  useLayoutEffect(() => {
    console.log('Child1 useLayoutEffect');
  });

  useEffect(() => {
    console.log('Child1 useEffect');
  });

  return (
    <>
      <div>
        子:
        {childProp}
      </div>
      <button type="button" onClick={childBtnClickFunc}>
        子ボタン
      </button>
    </>
  );
}

export default Child1;

結果(コンソールの表示内容)

// ページ表示後。
Child1 useLayoutEffect
Parent useLayoutEffect
App useLayoutEffect
Child1 useEffect
Parent useEffect
App useEffect

// 親ボタンを押下し、子のProp(親のState)を更新後。
Child1 useLayoutEffect
Parent useLayoutEffect
Child1 useEffect
Parent useEffect

// 子ボタンを押下し、親のStateを更新後。
Child1 useLayoutEffect
Parent useLayoutEffect
Child1 useEffect
Parent useEffect

初期描画時にのみ実行される場合

App.tsxの内容

import { ReactNode, useEffect, useLayoutEffect } from 'react';
import Parent from './Parent.tsx';

function App(): ReactNode {
  useLayoutEffect(() => {
    console.log('App useLayoutEffect');
  }, []);

  useEffect(() => {
    console.log('App useEffect');
  }, []);

  return (
    <>
      あいうえお
      <Parent />
    </>
  );
}

export default App;

Parent.tsxの内容

import {
  ReactNode, useEffect, useLayoutEffect, useState,
} from 'react';
import Child1 from './Child1.tsx';

function Parent(): ReactNode {
  const [child1Value, setChild1Value] = useState<string>('Child1の初期値');
  const [parentValue1, setParentValue1] = useState<string>('Parentの初期値');

  function parentBtnClickFunc() {
    alert('parentBtnClickFunc');
    setChild1Value('親からStateが変更されました');
  }

  useLayoutEffect(() => {
    console.log('Parent useLayoutEffect');
  }, []);

  useEffect(() => {
    console.log('Parent useEffect');
  }, []);

  return (
    <>
      <div>
        親:
        {parentValue1}
      </div>
      <button type="button" onClick={parentBtnClickFunc}>
        親ボタン
      </button>
      <Child1 childProp={child1Value} parentStateFunc={setParentValue1} />
    </>
  );
}

export default Parent;

Child1.tsxの内容

import { ReactNode, useEffect, useLayoutEffect } from 'react';

type Child1Props = {
  childProp: string,
  parentStateFunc: Function
};

function Child1({ childProp, parentStateFunc }: Child1Props): ReactNode {
  function childBtnClickFunc(): void {
    alert('childBtnClickFunc');
    parentStateFunc('子からStateが変更されました');
  }

  useLayoutEffect(() => {
    console.log('Child1 useLayoutEffect');
  }, []);

  useEffect(() => {
    console.log('Child1 useEffect');
  }, []);

  return (
    <>
      <div>
        子:
        {childProp}
      </div>
      <button type="button" onClick={childBtnClickFunc}>
        子ボタン
      </button>
    </>
  );
}

export default Child1;

結果(コンソールの表示内容)

// ページ表示後。
Child1 useLayoutEffect
Parent useLayoutEffect
App useLayoutEffect
Child1 useEffect
Parent useEffect
App useEffect

// 親ボタンを押下し、子のProp(親のState)を更新後。
※ 追加で表示されない。

// 子ボタンを押下し、親のStateを更新後。
※ 追加で表示されない。

特定のStateが更新された時に実行される場合

App.tsxの内容

import { ReactNode, useEffect, useLayoutEffect } from 'react';
import Parent from './Parent.tsx';

function App(): ReactNode {
  useLayoutEffect(() => {
    console.log('App useLayoutEffect');
  }, []);

  useEffect(() => {
    console.log('App useEffect');
  }, []);

  return (
    <>
      あいうえお
      <Parent />
    </>
  );
}

export default App;

Parent.tsxの内容

import {
  ReactNode, useEffect, useLayoutEffect, useState,
} from 'react';
import Child1 from './Child1.tsx';

function Parent(): ReactNode {
  const [child1Value, setChild1Value] = useState<string>('Child1の初期値');
  const [parentValue1, setParentValue1] = useState<string>('Parentの初期値');

  function parentBtnClickFunc() {
    alert('parentBtnClickFunc');
    setChild1Value('親からStateが変更されました');
  }

  useLayoutEffect(() => {
    console.log('Parent useLayoutEffect');
  }, [parentValue1]);

  useEffect(() => {
    console.log('Parent useEffect');
  }, [parentValue1]);

  return (
    <>
      <div>
        親:
        {parentValue1}
      </div>
      <button type="button" onClick={parentBtnClickFunc}>
        親ボタン
      </button>
      <Child1 childProp={child1Value} parentStateFunc={setParentValue1} />
    </>
  );
}

export default Parent;

Child1.tsxの内容

import { ReactNode, useEffect, useLayoutEffect } from 'react';

type Child1Props = {
  childProp: string,
  parentStateFunc: Function
};

function Child1({ childProp, parentStateFunc }: Child1Props): ReactNode {
  function childBtnClickFunc(): void {
    alert('childBtnClickFunc');
    parentStateFunc('子からStateが変更されました');
  }

  useLayoutEffect(() => {
    console.log('Child1 useLayoutEffect');
  }, [childProp]);

  useEffect(() => {
    console.log('Child1 useEffect');
  }, [childProp]);

  return (
    <>
      <div>
        子:
        {childProp}
      </div>
      <button type="button" onClick={childBtnClickFunc}>
        子ボタン
      </button>
    </>
  );
}

export default Child1;

結果(コンソールの表示内容)

// ページ表示後。
Child1 useLayoutEffect
Parent useLayoutEffect
App useLayoutEffect
Child1 useEffect
Parent useEffect
App useEffect

// 親ボタンを押下し、子のProp(親のState)を更新後。
Child1 useLayoutEffect
Child1 useEffect

// 子ボタンを押下し、親のStateを更新後。
Parent useLayoutEffect
Parent useEffect

useState で fetch してState更新

Child1.tsxの内容

import { ReactNode, useEffect, useLayoutEffect } from 'react';

type Child1Props = {
  childProp: string,
  parentStateFunc: Function
};

function Child1({ childProp, parentStateFunc }: Child1Props): ReactNode {
  const [showText, setShowText] = useState('初期値');

  const fetchData = async () => {
    const response = await fetch('http://localhost:3001');
    const data = await response.text();
    return data;
  };

  useEffect(() => {
    fetchData().then((text:string) => {
      setShowText(text);
    });
  }, []);

  return (
    <>
      <div>
        子:
        {childProp}
      </div>
      <div>
        {showText}
      </div>
    </>
  );
}

export default Child1;
同一カテゴリの記事