Reactコンポーネントが再レンダリングされる理由を追跡する


156

コンポーネントがReactで再レンダリングされる原因をデバッグする体系的なアプローチはありますか?シンプルなconsole.log()を使用して、何回レンダリングされるかを確認しましたが、私の場合、コンポーネントが複数回(つまり4回)レンダリングする原因を突き止めるのに問題があります。タイムラインやすべてのコンポーネントツリーのレンダリングと順序を表示するツールはありますか?


たぶん、shouldComponentUpdate自動コンポーネント更新を無効にして、そこからトレースを開始するために使用できます。詳細については、こちらをご覧ください:facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr

@jpdelatorreの答えは正しいです。一般に、Reactの長所の1つは、コードを調べることで、チェーンまで遡ってデータフローを簡単に追跡できることです。拡張子はデベロッパーツールリアクトことを支援することができます。また、私のReduxアドオンカタログの一部としてReactコンポーネントの再レンダリングを視覚化/追跡するため便利なツールのリストと、[Reactパフォーマンスモニタリング]に関する多数の記事があります(htt
markerikson

回答:


253

外部依存関係のない短いスニペットが必要な場合は、これが便利です

componentDidUpdate(prevProps, prevState) {
  Object.entries(this.props).forEach(([key, val]) =>
    prevProps[key] !== val && console.log(`Prop '${key}' changed`)
  );
  if (this.state) {
    Object.entries(this.state).forEach(([key, val]) =>
      prevState[key] !== val && console.log(`State '${key}' changed`)
    );
  }
}

これは、関数コンポーネントの更新を追跡するために使用する小さなフックです

function useTraceUpdate(props) {
  const prev = useRef(props);
  useEffect(() => {
    const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
      if (prev.current[k] !== v) {
        ps[k] = [prev.current[k], v];
      }
      return ps;
    }, {});
    if (Object.keys(changedProps).length > 0) {
      console.log('Changed props:', changedProps);
    }
    prev.current = props;
  });
}

// Usage
function MyComponent(props) {
  useTraceUpdate(props);
  return <div>{props.children}</div>;
}

5
@ yarden.refaeli ifブロックを使用する理由がわかりません。短くて簡潔。
Isaac

これに加えて、状態の一部が更新されていることがわかり、どこで、またはその理由が明らかでない場合は、setState(クラスコンポーネント内の)メソッドをオーバーライドsetState(...args) { super.setState(...args) }し、デバッガーにブレークポイントを設定して、デバッガーでできるようにします。状態を設定する関数までさかのぼります。
redbmk

フック関数はどのように使用しますか?useTraceUpdateあなたが書いたようにそれを定義した後、私は正確にどこに電話するべきですか?
デーモン

関数コンポーネントでは、このように使用でき、function MyComponent(props) { useTraceUpdate(props); }小道具が変更されるたびにログに記録されます
Jacob Rask

1
@DawsonBおそらくそのコンポーネントに状態this.stateがないため、未定義です。
Jacob Rask

67

以下は、Reactコンポーネントが再レンダリングするいくつかのインスタンスです。

  • 親コンポーネントの再レンダリング
  • this.setState()コンポーネント内で呼び出す。これは、次のコンポーネントのライフサイクルメソッドトリガーされますshouldComponentUpdate> componentWillUpdate> render>をcomponentDidUpdate
  • コンポーネントのの変更props。これが引き金となりますcomponentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdateconnectの方法をreact-reduxトリガーこのReduxのストアに適用変更がある場合)
  • this.forceUpdate似ている呼び出しthis.setState

内部にチェックを実装してshouldComponentUpdate返すことにより、コンポーネントの再レンダリングを最小限に抑えることができますfalse必要がない場合は。

または、ステートレスコンポーネントを使用する方法もありReact.PureComponent ます。純粋でステートレスなコンポーネントは、プロップに変更があった場合にのみ再レンダリングされます。


6
Nitpick:「ステートレス」とは、クラス構文で定義されているか、関数構文で定義されているかに関係なく、状態を使用しないコンポーネントを意味します。また、機能コンポーネントは常に再レンダリングされます。変更時に再レンダリングのみを実施shouldComponentUpdateするにはReact.PureComponent、を使用するか、を拡張する必要があります。
markerikson 16

1
ステートレス/機能コンポーネントが常に再レンダリングされるのは正しいことです。私の答えを更新します。
jpdelatorre 16

いつ、いつ、なぜ機能コンポーネントが常に再レンダリングされるのかを明確にできますか?アプリでかなりの機能コンポーネントを使用しています。
jasan 2016

たとえば、コンポーネントを作成する機能的な方法を使用している場合でもconst MyComponent = (props) => <h1>Hello {props.name}</h1>;(たとえば、ステートレスコンポーネントです)。親コンポーネントが再レンダリングされるたびに再レンダリングします。
jpdelatorre 16

2
これは確かに素晴らしい答えですが、実際の質問には答えません。-再レンダリングをトリガーしたものをトレースする方法。Jacob Rの回答は、実際の問題に対する回答を提供することに有望に見えます。
サヌジ

10

@jpdelatorreの回答は、Reactコンポーネントが再レンダリングされる一般的な理由を強調するのに優れています。

私は、小道具が変わるとき、少し深く1つのインスタンスに飛び込みたかっただけです。Reactコンポーネントの再レンダリングの原因をトラブルシューティングすることは一般的な問題であり、私の経験では、この問題を追跡する多くの場合、変更されているプロップを特定する必要があります。

Reactコンポーネントは、新しい小道具を受け取るたびに再レンダリングされます。彼らは次のような新しい小道具を受け取ることができます:

<MyComponent prop1={currentPosition} prop2={myVariable} />

またはMyComponentreduxストアに接続されている場合:

function mapStateToProps (state) {
  return {
    prop3: state.data.get('savedName'),
    prop4: state.data.get('userCount')
  }
}

いつでもの値はprop1prop2prop3、またはprop4変更がMyComponent再レンダリングされます。4つの小道具を使用すると、変更する小道具のconsole.log(this.props)最初にrenderブロックの。しかし、より複雑なコンポーネントとより多くの小道具で、この方法は受け入れられません。

以下に、便利なアプローチ(便宜上lodashを使用)で、コンポーネントの再レンダリングを引き起こしているプロップの変更を特定します。

componentWillReceiveProps (nextProps) {
  const changedProps = _.reduce(this.props, function (result, value, key) {
    return _.isEqual(value, nextProps[key])
      ? result
      : result.concat(key)
  }, [])
  console.log('changedProps: ', changedProps)
}

このスニペットをコンポーネントに追加すると、疑わしい再レンダリングを引き起こしている原因を明らかにするのに役立ち、多くの場合、これはコンポーネントにパイプされている不要なデータを明らかにするのに役立ちます。


3
現在は呼び出されてUNSAFE_componentWillReceiveProps(nextProps)おり、非推奨です。「このライフサイクルは以前に名前が付けられていましたcomponentWillReceiveProps。その名前はバージョン17まで機能します。」Reactのドキュメントから。
エミールベルジェロン

1
コンポーネントが実際に更新された原因を見つけたいだけなので、componentDidUpdateを使用して同じことを実現できます。
シャープな参照

5

奇妙なことは誰もその答えを出していないが、特に小道具の変更はほとんど常に深くネストされているので、私はそれが非常に便利だと思う。

フックファンボーイ:

import deep_diff from "deep-diff";
const withPropsChecker = WrappedComponent => {
  return props => {
    const prevProps = useRef(props);
    useEffect(() => {
      const diff = deep_diff.diff(prevProps.current, props);
      if (diff) {
        console.log(diff);
      }
      prevProps.current = props;
    });
    return <WrappedComponent {...props} />;
  };
};

「古い」学校のファンボーイ:

import deep_diff from "deep-diff";
componentDidUpdate(prevProps, prevState) {
      const diff = deep_diff.diff(prevProps, this.props);
      if (diff) {
        console.log(diff);
      }
}

PS私は今でもHOC(高次コンポーネント)を使用することを好みます。なぜなら、時々、小道具を上部で分解し、ジェイコブのソリューションがうまく適合しないためです。

免責事項:パッケージ所有者とは一切関係ありません。何十回もクリックして深くネストされたオブジェクトの違いを見つけようとするのは面倒です。



2

プロップの変更だけでなく、フックと機能コンポーネントを使用すると、再レンダリングが発生する可能性があります。私が使い始めたのはかなり手作業のログです。私はたくさん助けてくれました。あなたもそれが役に立つかもしれません。

この部分をコンポーネントのファイルに貼り付けます。

const keys = {};
const checkDep = (map, key, ref, extra) => {
  if (keys[key] === undefined) {
    keys[key] = {key: key};
    return;
  }
  const stored = map.current.get(keys[key]);

  if (stored === undefined) {
    map.current.set(keys[key], ref);
  } else if (ref !== stored) {
    console.log(
      'Ref ' + keys[key].key + ' changed',
      extra ?? '',
      JSON.stringify({stored}).substring(0, 45),
      JSON.stringify({now: ref}).substring(0, 45),
    );
    map.current.set(keys[key], ref);
  }
};

メソッドの最初に、WeakMap参照を保持します。

const refs = useRef(new WeakMap());

次に、各「疑わしい」呼び出し(小道具、フック)の後に、次のように記述します。

const example = useExampleHook();
checkDep(refs, 'example ', example);

1

上記の回答は非常に役立ちます。再レンダリングの原因を検出する特定の方法を誰かが探している場合に備えて、このライブラリredux-loggerを見つけました非常に役に立ちました。

あなたができることは、ライブラリを追加し、次のように状態(ドキュメントにあります)間の差分を有効にすることです

const logger = createLogger({
    diff: true,
});

そして、ミドルウェアをストアに追加します。

次に、console.log()テストするコンポーネントのレンダリング関数にa を配置します。

次に、アプリを実行してコンソールログを確認します。状態の違いが表示される直前にログがあり、そこで(nextProps and this.props)レンダリングが本当に必要かどうかを判断できますここに画像の説明を入力してください

上の画像と同様に、差分キーも表示されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.