Проследите, почему компонент React выполняет рендеринг

156

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

jasan
источник
Может быть, вы могли бы использовать, shouldComponentUpdateчтобы отключить автоматическое обновление компонентов, а затем начать свою трассировку оттуда. Дополнительную информацию можно найти здесь: facebook.github.io/react/docs/optimizing-performance.html
Реза Садр
@jpdelatorre ответ правильный. В общем, одна из сильных сторон React заключается в том, что вы можете легко отслеживать поток данных по цепочке, просматривая код. Расширение React DevTools может помочь с этим. Кроме того, у меня есть список полезных инструментов для визуализации / отслеживания повторного рендеринга компонентов React в моем каталоге дополнений Redux , а также ряд статей на тему [Мониторинг производительности React] (htt
markerikson

Ответы:

254

Если вы хотите короткий фрагмент без каких-либо внешних зависимостей, я считаю это полезным

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); }и он будет регистрировать всякий раз, когда изменяется реквизит
Джейкоб Раск
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 компонентов без сохранения состояния. Чистые и не сохраняющие состояния компоненты повторно визуализируются только при наличии изменений в его реквизите.

jpdelatorre
источник
6
Nitpick: «без сохранения состояния» означает просто любой компонент, который не использует состояние, независимо от того, определен ли он с помощью синтаксиса класса или функционального синтаксиса. Кроме того, функциональные компоненты всегда перерисовываются. Вам нужно либо использовать shouldComponentUpdate, либо расширять React.PureComponent, чтобы обеспечить только повторный рендеринг при изменении.
маркериксон
1
Вы правы насчет того, что компонент без состояния / функциональный всегда рендерится заново. Обновлю мой ответ.
jpdelatorre
Вы можете уточнить, когда и почему функциональные компоненты всегда повторно визуализируются? Я использую довольно много функциональных компонентов в моем приложении.
Ясан
Так что даже если вы используете функциональный способ создания вашего компонента, например const MyComponent = (props) => <h1>Hello {props.name}</h1>;(это компонент без состояния). Он будет перерисовываться всякий раз, когда родительский компонент перерисовывается.
jpdelatorre
2
Это отличный ответ наверняка, но он не отвечает на реальный вопрос - Как отследить, что вызвало повторную визуализацию. Ответ Джейкоба R выглядит многообещающе в ответе на реальную проблему.
Санудж
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
Вы можете добиться того же с помощью 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 (компонент более высокого порядка), потому что иногда вы деструктурируете свои реквизиты сверху, и решение Джейкоба не подходит

Отказ от ответственности: Нет связи с владельцем пакета. Простое нажатие в десятки раз, чтобы попытаться обнаружить разницу в глубоко вложенных объектах, - это боль в.

ZenVentzi
источник
4

Теперь есть доступ к этому на npm:

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

(Раскрытие, я опубликовал его) Обновление: Разработано на основе кода Джейкоба Раска

Дамиан Грин
источник
14
Это практически тот же код, который написал Джейкоб. Мог зачислить его туда.
Кристиан Ивичевич
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()в функцию рендеринга компонента, который вы хотите проверить.

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

Это будет похоже на изображение выше вместе с ключом diff.

pritesh
источник