Bir React bileşeninin neden yeniden oluşturulduğunu izleyin


181

React'te bir bileşenin yeniden işlemesine neden olan şeyi hatalarını ayıklamak için sistematik bir yaklaşım var mı? Kaç kez işlediğini görmek için basit bir console.log () koydum, ancak benim durumumda bileşenin birden çok kez, yani (4 kez) oluşturmasına neyin neden olduğunu bulmakta güçlük çekiyorum. Bir zaman çizelgesini ve / veya tüm bileşen ağacının işleyişini ve sırasını gösteren bir araç var mı?


Belki shouldComponentUpdateotomatik bileşen güncellemesini devre dışı bırakıp ardından izinizi oradan başlatmak için kullanabilirsiniz. Daha fazla bilgiyi burada bulabilirsiniz: facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr

@jpdelatorre 'nin yanıtı doğru. Genel olarak, React'in güçlü yönlerinden biri, koda bakarak veri akışını zincirden geri alabilmenizdir. DevTools uzatma Tepki bununla kutu yardım. Ayrıca, Redux eklenti kataloğumun bir parçası olarak React bileşenini yeniden oluşturmayı görselleştirmek / izlemek için yararlı araçların bir listesi ve [React performans izleme] hakkında bir dizi makale var (
http

Yanıtlar:


283

Herhangi bir harici bağımlılık içermeyen kısa bir pasaj istiyorsanız, bunu yararlı buluyorum

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

İşte bileşenlere yönelik güncellemeleri izlemek için kullandığım küçük bir kanca

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 Bir if bloğuna sahip olmak için bir neden göremiyorum. Kısa ve öz.
Isaac

Bununla birlikte, bir durum parçasının güncellendiğini fark ederseniz ve nerede veya neden olduğu açık değilse, setStateyöntemi (bir sınıf bileşeninde) ile geçersiz kılabilir setState(...args) { super.setState(...args) }ve ardından hata ayıklayıcınızda daha sonra yapabileceğiniz bir kesme noktası belirleyebilirsiniz. durumu ayarlayan işleve geri dönmek için.
redbmk

Kanca işlevini tam olarak nasıl kullanırım? useTraceUpdateSenin yazdığın gibi tanımladıktan sonra tam olarak nerede arayacağım ?
damon

Bir fonksiyon bileşeni olarak, bu gibi kullanabilirsiniz function MyComponent(props) { useTraceUpdate(props); }ve her sahne değişiklikleri günlüğe kaydeder
Jacob Rask

1
@DawsonB muhtemelen bu bileşende herhangi bir durumunuz yok, bu yüzden this.statetanımsız.
Jacob Rask

69

İşte bir React bileşeninin yeniden oluşturacağı bazı durumlar.

  • Üst bileşen yeniden oluşturucu
  • this.setState()Bileşen içinde arama . Bu, aşağıdaki bileşen yaşam döngüsü yöntemleri tetikler shouldComponentUpdate> componentWillUpdate> render>componentDidUpdate
  • Bileşenlerdeki değişiklikler props. Bu irade tetik componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate( connectmetodu react-reduxtetikleyicisi bu Redux deposunda uygulanabilir değişiklikler olduğunda)
  • this.forceUpdatebenzer çağrıthis.setState

Bileşeninizin yeniden oluşturulmasını, içinde bir kontrol uygulayarak shouldComponentUpdateve falsegerekmiyorsa geri göndererek en aza indirebilirsiniz .

Başka bir yol da durum bilgisiz bileşenleri kullanmaktır React.PureComponent . Saf ve durumsuz bileşenler, yalnızca sahne öğelerinde değişiklik olduğunda yeniden oluşturulur.


7
Nitpick: "durumsuz", ister sınıf sözdizimi ister işlevsel sözdizimi ile tanımlanmış olsun, durumu kullanmayan herhangi bir bileşen anlamına gelir. Ayrıca, işlevsel bileşenler her zaman yeniden oluşturulur. Yalnızca değişiklik olduğunda yeniden oluşturmayı zorunlu kılmak için kullanmanız shouldComponentUpdateveya genişletmeniz gerekir React.PureComponent.
markerikson

1
Durumsuz / işlevsel bileşen her zaman yeniden oluşturulur konusunda haklısınız. Cevabımı güncelleyecek.
jpdelatorre

işlevsel bileşenlerin ne zaman ve neden her zaman yeniden işlendiğini açıklayabilir misiniz? Uygulamamda oldukça fazla işlevsel bileşen kullanıyorum.
jasan

Dolayısıyla, bileşeninizi oluşturmanın işlevsel yolunu kullansanız bile, örneğin const MyComponent = (props) => <h1>Hello {props.name}</h1>;(bu durumsuz bir bileşendir). Üst bileşen her yeniden oluşturulduğunda yeniden oluşturulacaktır.
jpdelatorre

2
Bu kesinlikle harika bir cevap, ancak asıl soruyu cevaplamıyor - Bir yeniden oluşturmayı neyin tetiklediğinin izini sürmek. Jacob R.'nin cevabı, gerçek soruna cevap verme konusunda umut verici görünüyor.
Sanuj

10

@ jpdelatorre'nin cevabı, bir React bileşeninin neden yeniden oluşturulabileceğinin genel nedenlerini vurgulamakta harika.

Sadece bir örneğe biraz daha derin dalmak istedim: sahne değiştiğinde . Bir React bileşeninin yeniden oluşturulmasına neyin sebep olduğunu sorun giderme yaygın bir sorundur ve deneyimlerime göre, çoğu zaman bu sorunu takip etmek, hangi props'ların değiştiğinin belirlenmesini içerir .

React bileşenleri, yeni sahne aldıklarında yeniden oluşturulur. Aşağıdakiler gibi yeni eşyalar alabilirler:

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

veya MyComponentbir redux mağazasına bağlıysa:

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

Herzaman değeri prop1, prop2, prop3veya prop4değişiklikler MyComponentyeniden işlerler. 4 sahne console.log(this.props)ile, renderbloğun başlangıcına bir tane koyarak hangi eşyanın değiştiğini izlemek çok zor değil . Bununla birlikte, daha karmaşık bileşenler ve giderek daha fazla sahne donanımı ile bu yöntem savunulamaz.

İşte hangi prop değişikliklerinin bir bileşenin yeniden oluşturulmasına neden olduğunu belirlemek için yararlı bir yaklaşım ( kolaylık sağlamak için lodash kullanarak ):

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

Bu pasajı bileşeninize eklemek, şüpheli yeniden işlemelere neden olan suçluyu ortaya çıkarmaya yardımcı olabilir ve çoğu zaman bu, bileşenlere iletilen gereksiz verilere ışık tutmaya yardımcı olur.


3
Şimdi çağrıldı UNSAFE_componentWillReceiveProps(nextProps)ve kullanımdan kaldırıldı. "Bu yaşam döngüsü önceden adlandırılmıştı componentWillReceiveProps. Bu ad, sürüm 17'ye kadar çalışmaya devam edecek." Gönderen belgelere Tepki .
Emile Bergeron

1
Aynı şeyi componentDidUpdate ile de elde edebilirsiniz, ki bu yine de tartışmasız daha iyidir, çünkü yalnızca bir bileşenin gerçekten güncellenmesine neyin sebep olduğunu bulmak istiyorsunuz.
keskin bkz

5

Garip kimse bu cevabı vermedi, ancak bunu çok faydalı buluyorum, özellikle de sahne değişiklikleri neredeyse her zaman derinlemesine iç içe geçtiğinden.

Hooks hayranları:

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

"Eski" -okul hayranları:

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

Not: Hala HOC (daha yüksek seviye bileşen) kullanmayı tercih ediyorum çünkü bazen eşyalarınızı tepede tahrip ettiniz ve Jacob'ın çözümü iyi uymuyor

Sorumluluk Reddi: Paket sahibiyle herhangi bir bağlantı yoktur. Derinlemesine iç içe geçmiş nesnelerdeki farkı tespit etmeye çalışmak için onlarca kez tıklamak,.


4

Kancaları ve işlevsel bileşenleri kullanmak, yalnızca pervane değişikliği değil, yeniden oluşturmaya neden olabilir. Kullanmaya başladığım şey oldukça manuel bir günlük. Bana çok yardımcı oldum Sen de yararlı bulabilirsin.

Bu parçayı bileşenin dosyasına yapıştırıyorum:

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

Yöntemin başında bir WeakMap referansı tutuyorum:

const refs = useRef(new WeakMap());

Sonra her "şüpheli" aramadan sonra (sahne donanımı, kancalar) yazıyorum:

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

1

Yukarıdaki cevaplar çok faydalıdır, eğer herhangi biri yeniden oluşturmanın nedenini tespit etmek için özel bir yöntem arıyorsa, bu kitaplık redux-logger'ı çok yararlı buldum .

Yapabileceğiniz şey, kitaplığı eklemek ve durumlar arasında (belgelerde var) aşağıdaki gibi farkı etkinleştirmektir:

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

Ve mağazaya ara yazılım ekleyin.

Ardından console.log()test etmek istediğiniz bileşenin render fonksiyonuna bir ekleyin.

Daha sonra uygulamanızı çalıştırabilir ve konsol günlüklerini kontrol edebilirsiniz. Hemen öncesinde bir günlük varsa, durum arasındaki farkı size gösterir (nextProps and this.props)ve orada gerçekten render gerekip gerekmediğine karar verebilirsiniz.görüntü açıklamasını buraya girin

Diferansiyel anahtarı ile birlikte yukarıdaki resme benzer olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.