Простежте, чому компонент React повторно відображає


156

Чи є системний підхід до налагодження того, що викликає повторний показ компонента в React? Я поклав простий console.log (), щоб побачити, скільки разів він відображається, але у мене виникають проблеми з з'ясуванням того, що викликає показник компонента кілька разів, тобто (4 рази) в моєму випадку. Чи існує інструмент, який показує хронологічну шкалу та / або всі види дерев і компонентів?


Можливо, ви можете використати shouldComponentUpdateдля відключення автоматичного оновлення компонентів, а потім почати свій шлях звідти. Більше інформації можна знайти тут: facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr

Відповідь @jpdelatorre правильна. Загалом, однією з сильних сторін React є те, що ви можете легко простежити потік даних назад по ланцюгу, переглянувши код. Розширення React DevTools може допомогти у цьому. Також у мене є список корисних інструментів для візуалізації / відстеження повторного відображення компонентів React як частини мого каталогу Redux addons , та ряд статей про [Моніторинг продуктивності реагування] (
https

Відповіді:


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. Короткий і стислий.
Ісаак

Поряд з цим, якщо ви виявите, що фрагмент стану оновлюється, і не очевидно, де або чому, ви можете замінити setStateметод (у складі класу), setState(...args) { super.setState(...args) }а потім встановити точку перелому у своєму відладчику, який ви зможете потім простежити назад до функції, що встановлює стан.
redbmk

Як саме я використовую функцію гачка? Куди саме я повинен зателефонувати, useTraceUpdateколи я визначив це так, як ви його написали?
прокляття

У функціональному компоненті ви можете використовувати його так, function MyComponent(props) { useTraceUpdate(props); }і він буде реєструватися, коли змінюється реквізит
Jacob Rask

1
@DawsonB у вас, ймовірно, немає жодного стану в цьому компоненті, тому this.stateце не визначено.
Яків Раск

67

Ось кілька випадків, коли компонент 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 або безкомпонентні компоненти. Чисті та безпартійні компоненти відображаються лише тоді, коли є зміни реквізиту.


6
Nitpick: "без стану" просто означає будь-який компонент, який не використовує стан, незалежно від того, чи визначено це синтаксисом класу або функціональним синтаксисом. Також функціональні компоненти завжди відтворюються. Вам потрібно або використовувати shouldComponentUpdate, або розширити React.PureComponent, щоб примусити лише повторне відображення змін.
markerikson

1
Ви маєте рацію щодо того, що стан без функцій без функціонування завжди відображається. Оновлю мою відповідь.
jpdelatorre

Ви можете уточнити, коли і чому функціональні компоненти завжди відтворюються? Я використовую досить багато функціональних компонентів у своєму додатку.
Ясан

Тож навіть якщо ви використовуєте функціональний спосіб створення свого компонента, наприклад const MyComponent = (props) => <h1>Hello {props.name}</h1>; (це компонент без стану). Він буде повторно відображатися кожного разу, коли батьківський компонент повторно відображатиметься.
jpdelatorre

2
Це чудова відповідь напевно, але вона не дає відповіді на реальне запитання, - як простежити, що спричинило повторну візуалізацію. Відповідь Якова Р виглядає багатообіцяючою у відповіді на реальну проблему.
Сануй

10

@ jpdelatorre відповідь чудово підкреслює загальні причини, через які компонент React може повторно відображатись.

Я просто хотів зануритися трохи глибше в один екземпляр: коли змінюється реквізит . Виправлення неполадок того, що викликає повторне відображення компонента React, є моїм досвідом, і багато разів, відстежуючи цю проблему, передбачає визначення реквізиту, який змінюється .

Реагуючі компоненти повторно відображаються щоразу, коли отримують нові реквізити. Вони можуть отримувати нові реквізити:

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

або якщо MyComponentвін підключений до магазину редукцій:

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

Додавання цього фрагмента до вашого компонента може допомогти виявити винуватця, що викликає сумнівні повторні рендери, і багато разів це допомагає пролити світло на непотрібні дані, що перетворюються на компоненти.


3
Зараз він називається UNSAFE_componentWillReceiveProps(nextProps)і застарілий. "Цей життєвий цикл раніше був названий componentWillReceiveProps. Це ім'я буде працювати до версії 17." З документації React .
Еміль Бержерон

1
Ви можете домогтися того ж з компонентомDidUpdate, який, мабуть, краще все-таки, оскільки ви хочете лише дізнатися, що саме спричинило оновлення компонента.
див.

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

На початку методу я зберігаю посилання на слабку карту:

const refs = useRef(new WeakMap());

Потім після кожного "підозрілого" дзвінка (реквізити, гачки) я пишу:

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

1

Наведені вище відповіді дуже корисні, на всякий випадок, якщо хтось шукає специфічний метод виявлення причини рендерингу, тоді я знайшов цю бібліотеку redux-logger дуже корисною.

Що ви можете зробити, це додати бібліотеку та включити різницю між станом (він є в документах), наприклад:

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

І додайте проміжне програмне забезпечення в магазині.

Потім введіть console.log()у функцію візуалізації компонент, який ви хочете протестувати.

Потім ви можете запустити додаток і перевірити наявність консольних журналів. Де б не було журналу, перш ніж він покаже вам різницю між станом, (nextProps and this.props)і ви можете вирішити, чи дійсно потрібна візуалізація тамвведіть тут опис зображення

Це буде схоже на наведене вище зображення разом із клавішею diff.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.