Lacak mengapa komponen Bereaksi merender ulang

156

Apakah ada pendekatan sistematis untuk men-debug apa yang menyebabkan komponen untuk merender ulang di Bereaksi? Saya meletakkan console.log sederhana () untuk melihat berapa banyak waktu render, tetapi saya mengalami kesulitan mencari tahu apa yang menyebabkan komponen untuk membuat beberapa kali yaitu (4 kali) dalam kasus saya. Apakah ada alat yang ada yang menunjukkan timeline dan / atau semua komponen merender dan memesan pohon?

jasan
sumber
Mungkin Anda dapat menggunakan shouldComponentUpdateuntuk menonaktifkan pembaruan komponen otomatis dan kemudian memulai jejak Anda dari sana. Informasi lebih lanjut dapat ditemukan di sini: facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr
Jawaban @jpdelatorre benar. Secara umum, salah satu kekuatan Bereaksi adalah bahwa Anda dapat dengan mudah melacak aliran data kembali ke rantai dengan melihat kode. The Bereaksi DevTools ekstensi dapat membantu dengan itu. Juga, saya memiliki daftar alat yang berguna untuk memvisualisasikan / melacak React rendering komponen sebagai bagian dari katalog addux Redux saya , dan sejumlah artikel di [React performance monitoring] (htt
markerikson

Jawaban:

254

Jika Anda ingin cuplikan singkat tanpa ketergantungan eksternal apa pun, saya merasa ini berguna

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`)
    );
  }
}

Berikut adalah kait kecil yang saya gunakan untuk melacak pembaruan ke komponen fungsi

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>;
}
Jacob Rask
sumber
5
@ yarden.refaeli Saya tidak melihat alasan untuk memiliki blok if. Singkat dan ringkas.
Isaac
Bersamaan dengan ini, jika Anda menemukan bagian dari keadaan sedang diperbarui dan tidak jelas di mana atau mengapa, Anda dapat mengganti setStatemetode (dalam komponen kelas) dengan setState(...args) { super.setState(...args) }dan kemudian menetapkan breakpoint di debugger Anda yang kemudian dapat Anda peroleh. untuk melacak kembali ke pengaturan fungsi negara.
redbmk
Bagaimana tepatnya cara saya menggunakan fungsi hook? Di mana tepatnya saya harus menelepon useTraceUpdatesetelah saya mendefinisikannya saat Anda menulisnya?
Damon
Dalam komponen fungsi, Anda dapat menggunakannya seperti ini function MyComponent(props) { useTraceUpdate(props); }dan itu akan masuk setiap kali props berubah
Jacob Rask
1
@ DawsonB Anda mungkin tidak memiliki keadaan dalam komponen itu, jadi this.statetidak ditentukan.
Jacob Rask
67

Berikut adalah beberapa contoh yang komponen Bereaksi akan dirender ulang.

  • Rerender komponen induk
  • Memanggil this.setState()dalam komponen. Hal ini akan memicu metode siklus hidup komponen berikut shouldComponentUpdate> componentWillUpdate> render>componentDidUpdate
  • Perubahan komponen props. Akan memicu ini componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate( connectmetode react-reduxpemicu ini bila ada perubahan berlaku di toko Redux)
  • panggilan this.forceUpdateyang mirip denganthis.setState

Anda dapat meminimalkan rerender komponen Anda dengan menerapkan pemeriksaan di dalam Anda shouldComponentUpdatedan mengembalikan falsejika tidak perlu.

Cara lain adalah dengan menggunakan React.PureComponent atau komponen stateless. Komponen murni dan stateless hanya di-render ketika ada perubahan pada propsnya.

jpdelatorre
sumber
6
Nitpick: "stateless" hanya berarti komponen apa pun yang tidak menggunakan status, apakah itu didefinisikan dengan sintaksis kelas atau sintaksis fungsional. Juga, komponen fungsional selalu dirender ulang. Anda harus menggunakan shouldComponentUpdate, atau memperluas React.PureComponent, untuk menegakkan hanya rendering ulang pada perubahan.
markerikson
1
Anda benar tentang komponen stateless / fungsional yang selalu dirender ulang. Akan memperbarui jawaban saya.
jpdelatorre
dapatkah Anda mengklarifikasi, kapan dan mengapa komponen fungsional selalu dirender ulang? Saya menggunakan sedikit komponen fungsional di aplikasi saya.
jasan
Jadi, bahkan jika Anda menggunakan cara fungsional untuk membuat komponen Anda misalnya const MyComponent = (props) => <h1>Hello {props.name}</h1>;(itu adalah komponen stateless). Ini akan merender ulang setiap kali komponen induk dirender kembali.
jpdelatorre
2
Ini jawaban yang bagus, tapi itu tidak menjawab pertanyaan sebenarnya, - Bagaimana melacak apa yang memicu re-render. Jawaban Jacob R terlihat menjanjikan dalam memberikan jawaban untuk masalah nyata.
Sanuj
10

Jawaban @ jpdelatorre sangat bagus dalam menyoroti alasan umum mengapa komponen Bereaksi dapat di-render ulang.

Saya hanya ingin menyelam sedikit lebih dalam ke satu contoh: ketika alat peraga berubah . Pemecahan masalah apa yang menyebabkan komponen Bereaksi untuk di-render adalah masalah yang umum, dan dalam pengalaman saya banyak kali melacak masalah ini melibatkan menentukan alat peraga mana yang berubah .

Bereaksi komponen yang di-render setiap kali mereka menerima alat peraga baru. Mereka dapat menerima alat peraga baru seperti:

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

atau jika MyComponentterhubung ke toko redux:

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

Kapan saja nilai dari prop1 , prop2, prop3, atau prop4perubahan MyComponentakan kembali membuat. Dengan 4 alat peraga, tidak terlalu sulit untuk melacak alat peraga mana yang berubah dengan meletakkan console.log(this.props)pada awal renderblok itu. Namun dengan komponen yang lebih rumit dan lebih banyak alat peraga metode ini tidak dapat dipertahankan.

Berikut ini adalah pendekatan yang berguna (menggunakan lodash untuk kenyamanan) untuk menentukan perubahan prop mana yang menyebabkan komponen dirender ulang:

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

Menambahkan potongan ini ke komponen Anda dapat membantu mengungkap pelakunya yang menyebabkan perenderan ulang yang dipertanyakan, dan sering kali ini membantu menjelaskan data yang tidak perlu yang disalurkan ke komponen.

Cumulo Nimbus
sumber
3
Itu sekarang dipanggil UNSAFE_componentWillReceiveProps(nextProps)dan sudah usang. "Daur hidup ini sebelumnya bernama componentWillReceiveProps. Nama itu akan terus bekerja sampai versi 17." Dari dokumentasi Bereaksi .
Emile Bergeron
1
Anda dapat mencapai hal yang sama dengan componentDidUpdate, yang bisa dibilang lebih baik, karena Anda hanya ingin mencari tahu apa yang menyebabkan komponen benar-benar memperbarui.
Lihat lebih tajam
5

Tidak ada yang aneh telah memberikan jawaban itu tetapi saya merasa sangat berguna, terutama karena perubahan alat peraga hampir selalu sangat bersarang.

Fanboys kait:

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} />;
  };
};

"Old" - fanboys sekolah:

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

PS Saya masih lebih suka menggunakan HOC (komponen pesanan lebih tinggi) karena kadang-kadang Anda telah merusak alat peraga Anda di bagian atas dan solusi Yakub tidak cocok dengan baik

Penafian: Tidak ada afiliasi apa pun dengan pemilik paket. Hanya mengklik puluhan kali di sekitar untuk mencoba melihat perbedaan pada objek yang bersarang secara mendalam adalah rasa sakit di.

ZenVentzi
sumber
4

Sekarang ada pengait untuk ini tersedia di npm:

https://www.npmjs.com/package/use-trace-update

(Pengungkapan, saya menerbitkannya) Pembaruan: Dikembangkan berdasarkan kode Jacob Rask

Damian Green
sumber
14
Ini sebenarnya kode yang sama dengan yang diposting Yakub. Bisa saja dikreditkan di sana.
Christian Ivicevic
2

Menggunakan kait dan komponen fungsional, bukan hanya penyangga perubahan dapat menyebabkan rerender. Apa yang mulai saya gunakan adalah log yang agak manual. Saya banyak membantu saya. Anda mungkin menemukan itu berguna juga.

Saya tempel bagian ini di file komponen:

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);
  }
};

Di awal metode saya menyimpan referensi WeakMap:

const refs = useRef(new WeakMap());

Kemudian setelah setiap panggilan "mencurigakan" (alat peraga, kait) saya menulis:

const example = useExampleHook();
checkDep(refs, 'example ', example);
Miklos Jakab
sumber
1

Jawaban di atas sangat membantu, kalau-kalau ada yang mencari metode specfic untuk mendeteksi penyebab rerender maka saya menemukan perpustakaan ini redux-logger sangat membantu.

Yang bisa Anda lakukan adalah menambahkan perpustakaan dan mengaktifkan pembedaan antar negara (ada di dokumen) seperti:

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

Dan tambahkan middleware di toko.

Kemudian masukkan console.log()fungsi render dari komponen yang ingin Anda uji.

Kemudian Anda dapat menjalankan aplikasi Anda dan memeriksa log konsol. Di mana pun ada log tepat sebelum itu akan menunjukkan perbedaan antara negara (nextProps and this.props)dan Anda dapat memutuskan apakah render benar-benar diperlukan di sanamasukkan deskripsi gambar di sini

Ini akan mirip dengan gambar di atas bersama dengan tombol diff.

Pritesh
sumber