ReactuseEffectフックを最初のレンダリングで実行しないようにする


106

ドキュメントによると:

componentDidUpdate()更新が発生した直後に呼び出されます。このメソッドは、最初のレンダリングでは呼び出されません。

新しいuseEffect()フックを使用してシミュレートできますが、最初でも、すべてのレンダリングの後に実行されcomponentDidUpdate()ているようuseEffect()です。最初のレンダリングで実行されないようにするにはどうすればよいですか?

以下の例でわかるように、componentDidUpdateFunctionは最初のレンダリング中に印刷されますが、最初のレンダリング中には印刷されcomponentDidUpdateClassませんでした。

function ComponentDidUpdateFunction() {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {
    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

class ComponentDidUpdateClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidUpdate() {
    console.log("componentDidUpdateClass");
  }

  render() {
    return (
      <div>
        <p>componentDidUpdateClass: {this.state.count} times</p>
        <button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        >
          Click Me
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <ComponentDidUpdateFunction />
    <ComponentDidUpdateClass />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>


1
のような明示的な状態変数ではなく、レンダリングの数に基づいて何かを行うことが理にかなっている場合のユースケースは何countですか?
Aprillion

回答:


122

useRefフックを使用して、好きな可変値を格納できるので、それを使用して、useEffect関数が初めて実行されるかどうかを追跡できます。

エフェクトを実行するのと同じフェーズで実行する場合componentDidUpdateは、useLayoutEffect代わりに使用できます。

const { useState, useRef, useLayoutEffect } = React;

function ComponentDidUpdateFunction() {
  const [count, setCount] = useState(0);

  const firstUpdate = useRef(true);
  useLayoutEffect(() => {
    if (firstUpdate.current) {
      firstUpdate.current = false;
      return;
    }

    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

ReactDOM.render(
  <ComponentDidUpdateFunction />,
  document.getElementById("app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>


5
に置き換えようとしましuseRefuseStateが、セッターを使用すると再レンダリングがトリガーされました。これは割り当て時に発生しないため、これがfirstUpdate.current唯一の良い方法だと思います:)
Aprillion 2018年

2
DOMを変更または測定していないのに、なぜレイアウト効果を使用するのか説明してもらえますか?
ZenVentzi

5
@ZenVentziこの例では必要ありませんが、componentDidUpdateフックを使ってどのように模倣するかが問題だったので、それを使用しました。
Tholle

1
この回答に基づいて、ここでカスタムフックを作成しました。実装してくれてありがとう!
パトリック・ロバーツ

62

あなたはそれを次のようにカスタムフックに変えることができます:

import React, { useEffect, useRef } from 'react';

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        if (didMount.current) func();
        else didMount.current = true;
    }, deps);
}

export default useDidMountEffect;

使用例:

import React, { useState, useEffect } from 'react';

import useDidMountEffect from '../path/to/useDidMountEffect';

const MyComponent = (props) => {    
    const [state, setState] = useState({
        key: false
    });    

    useEffect(() => {
        // you know what is this, don't you?
    }, []);

    useDidMountEffect(() => {
        // react please run me if 'key' changes, but not on initial render
    }, [state.key]);    

    return (
        <div>
             ...
        </div>
    );
}
// ...

2
このアプローチでは、依存関係リストが配列リテラルではないという警告がスローされます。
プログラマー

1
プロジェクトでこのフックを使用していますが、警告は表示されませんでした。詳細を教えてください。
MehdiDehghani20年

1
@vsync最初のレンダリングで一度だけエフェクトを実行し、二度と実行したくない別のケースについて考えています
ProgrammingGuy20年

2
@vsync reactjs.org/docs/のメモセクションで具体的には、「エフェクトを実行して、(マウントとアンマウントで)1回だけクリーンアップする場合は、空の配列([])をとして渡すことができます。 2番目の引数。」これは私が観察した行動と一致します。
プログラミングガイ

9

useFirstRenderフォーム入力のフォーカスなどのケースを処理するための単純なフックを作成しました。

import { useRef, useEffect } from 'react';

export function useFirstRender() {
  const firstRender = useRef(true);

  useEffect(() => {
    firstRender.current = false;
  }, []);

  return firstRender.current;
}

ように、それはアウト開始しtrue、その後に切り替わり、falseuseEffect二度と一度だけ実行される、と。

コンポーネントで、次のものを使用します。

const firstRender = useFirstRender();
const phoneNumberRef = useRef(null);

useEffect(() => {
  if (firstRender || errors.phoneNumber) {
    phoneNumberRef.current.focus();
  }
}, [firstRender, errors.phoneNumber]);

あなたの場合、あなたはただを使うでしょうif (!firstRender) { ...


3

@ravi、渡されたアンマウント関数を呼び出しません。これがもう少し完全なバージョンです:

/**
 * Identical to React.useEffect, except that it never runs on mount. This is
 * the equivalent of the componentDidUpdate lifecycle function.
 *
 * @param {function:function} effect - A useEffect effect.
 * @param {array} [dependencies] - useEffect dependency list.
 */
export const useEffectExceptOnMount = (effect, dependencies) => {
  const mounted = React.useRef(false);
  React.useEffect(() => {
    if (mounted.current) {
      const unmount = effect();
      return () => unmount && unmount();
    } else {
      mounted.current = true;
    }
  }, dependencies);

  // Reset on unmount for the next mount.
  React.useEffect(() => {
    return () => mounted.current = false;
  }, []);
};


こんにちは@Whatabrain、非依存リストを渡すときにこのカスタムフックを使用する方法は?componentDidmountと同じであるが、次のような空ではありませんuseEffect(() => {...});
KevDing

1
@KevDingは、dependencies呼び出すときにパラメーターを省略するのと同じくらい単純である必要があります。
Whatabrain

0

@MehdiDehghani、ソリューションは完全に正常に機能します。追加する必要があるのは、マウントを解除し、didMount.current値をにリセットすることfalseです。このカスタムフックを別の場所で使用しようとすると、キャッシュ値が取得されません。

import React, { useEffect, useRef } from 'react';

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        let unmount;
        if (didMount.current) unmount = func();
        else didMount.current = true;

        return () => {
            didMount.current = false;
            unmount && unmount();
        }
    }, deps);
}

export default useDidMountEffect;

2
コンポーネントがアンマウントされた場合、再マウントされた場合、didMountはすでにに再初期化されているため、これが必要かどうかはわかりませんfalse
キャメロンイック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.