コンポーネントがReactで再レンダリングされる原因をデバッグする体系的なアプローチはありますか?シンプルなconsole.log()を使用して、何回レンダリングされるかを確認しましたが、私の場合、コンポーネントが複数回(つまり4回)レンダリングする原因を突き止めるのに問題があります。タイムラインやすべてのコンポーネントツリーのレンダリングと順序を表示するツールはありますか?
コンポーネントがReactで再レンダリングされる原因をデバッグする体系的なアプローチはありますか?シンプルなconsole.log()を使用して、何回レンダリングされるかを確認しましたが、私の場合、コンポーネントが複数回(つまり4回)レンダリングする原因を突き止めるのに問題があります。タイムラインやすべてのコンポーネントツリーのレンダリングと順序を表示するツールはありますか?
回答:
外部依存関係のない短いスニペットが必要な場合は、これが便利です
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>;
}
setState
(クラスコンポーネント内の)メソッドをオーバーライドsetState(...args) { super.setState(...args) }
し、デバッガーにブレークポイントを設定して、デバッガーでできるようにします。状態を設定する関数までさかのぼります。
useTraceUpdate
あなたが書いたようにそれを定義した後、私は正確にどこに電話するべきですか?
function MyComponent(props) { useTraceUpdate(props); }
小道具が変更されるたびにログに記録されます
this.state
がないため、未定義です。
以下は、Reactコンポーネントが再レンダリングするいくつかのインスタンスです。
this.setState()
コンポーネント内で呼び出す。これは、次のコンポーネントのライフサイクルメソッドトリガーされますshouldComponentUpdate
> componentWillUpdate
> render
>をcomponentDidUpdate
props
。これが引き金となりますcomponentWillReceiveProps
> shouldComponentUpdate
> componentWillUpdate
> render
> componentDidUpdate
(connect
の方法をreact-redux
トリガーこのReduxのストアに適用変更がある場合)this.forceUpdate
似ている呼び出しthis.setState
内部にチェックを実装してshouldComponentUpdate
返すことにより、コンポーネントの再レンダリングを最小限に抑えることができますfalse
必要がない場合は。
または、ステートレスコンポーネントを使用する方法もありReact.PureComponent
ます。純粋でステートレスなコンポーネントは、プロップに変更があった場合にのみ再レンダリングされます。
shouldComponentUpdate
するにはReact.PureComponent
、を使用するか、を拡張する必要があります。
const MyComponent = (props) => <h1>Hello {props.name}</h1>;
(たとえば、ステートレスコンポーネントです)。親コンポーネントが再レンダリングされるたびに再レンダリングします。
@jpdelatorreの回答は、Reactコンポーネントが再レンダリングされる一般的な理由を強調するのに優れています。
私は、小道具が変わるとき、少し深く1つのインスタンスに飛び込みたかっただけです。Reactコンポーネントの再レンダリングの原因をトラブルシューティングすることは一般的な問題であり、私の経験では、この問題を追跡する多くの場合、変更されているプロップを特定する必要があります。
Reactコンポーネントは、新しい小道具を受け取るたびに再レンダリングされます。彼らは次のような新しい小道具を受け取ることができます:
<MyComponent prop1={currentPosition} prop2={myVariable} />
またはMyComponent
reduxストアに接続されている場合:
function mapStateToProps (state) {
return {
prop3: state.data.get('savedName'),
prop4: state.data.get('userCount')
}
}
いつでもの値はprop1
、prop2
、prop3
、または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)
}
このスニペットをコンポーネントに追加すると、疑わしい再レンダリングを引き起こしている原因を明らかにするのに役立ち、多くの場合、これはコンポーネントにパイプされている不要なデータを明らかにするのに役立ちます。
UNSAFE_componentWillReceiveProps(nextProps)
おり、非推奨です。「このライフサイクルは以前に名前が付けられていましたcomponentWillReceiveProps
。その名前はバージョン17まで機能します。」Reactのドキュメントから。
奇妙なことは誰もその答えを出していないが、特に小道具の変更はほとんど常に深くネストされているので、私はそれが非常に便利だと思う。
フックファンボーイ:
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(高次コンポーネント)を使用することを好みます。なぜなら、時々、小道具を上部で分解し、ジェイコブのソリューションがうまく適合しないためです。
免責事項:パッケージ所有者とは一切関係ありません。何十回もクリックして深くネストされたオブジェクトの違いを見つけようとするのは面倒です。
これで、npmでこれを利用できるフックがあります。
https://www.npmjs.com/package/use-trace-update
(開示、私はそれを公開しました)更新:Jacob Raskのコードに基づいて開発されました
プロップの変更だけでなく、フックと機能コンポーネントを使用すると、再レンダリングが発生する可能性があります。私が使い始めたのはかなり手作業のログです。私はたくさん助けてくれました。あなたもそれが役に立つかもしれません。
この部分をコンポーネントのファイルに貼り付けます。
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);
上記の回答は非常に役立ちます。再レンダリングの原因を検出する特定の方法を誰かが探している場合に備えて、このライブラリredux-loggerを見つけました非常に役に立ちました。
あなたができることは、ライブラリを追加し、次のように状態(ドキュメントにあります)間の差分を有効にすることです
const logger = createLogger({
diff: true,
});
そして、ミドルウェアをストアに追加します。
次に、console.log()
テストするコンポーネントのレンダリング関数にa を配置します。
次に、アプリを実行してコンソールログを確認します。状態の違いが表示される直前にログがあり、そこで(nextProps and this.props)
レンダリングが本当に必要かどうかを判断できます
上の画像と同様に、差分キーも表示されます。
shouldComponentUpdate
自動コンポーネント更新を無効にして、そこからトレースを開始するために使用できます。詳細については、こちらをご覧ください:facebook.github.io/react/docs/optimizing-performance.html