machinelearningmastery.ru

Машинное обучение, нейронные сети, искусственный интеллект
Header decor

Home

Делать глупый React умным при повторном рендеринге

Дата публикации Oct 3, 2019

Я верил, что React достаточно умен, чтобы знать это ... но да?

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

Конечно, дерево было намного больше этого. Дерево не дошло до конца. Это сделало бесполезное повторное рендеринг еще более серьезным. Сверху вниз,Provider,ConnectedRouter,App,PersistGate,ComponentA,ComponentB, .... и так далее.

И я увидел, что не было никакого смыслаshouldComponentUpdate/PureComponent/memoв нашем приложении еще нет. Поэтому я решил использовать их, еще не зная, что меня ждет.

Вывод 1: родительский компонент в значительной степени не заботится о егоchildren

Я оптимизировал приложение и стал свидетелем интересного факта. Вот код, чтобы продемонстрировать это:

А ты думаешьChild1а такжеChild2собираются перерисовать наParentсоставные частиmouseEnter? Посмотрите, что происходит:

К сожалению.Они перерисовывают.Но я не вижу причины, почему (То же самое происходит дляclassкомпоненты, если вам интересно. Вы можете попробовать это!). У детей даже нет своих реквизитов или состояний, чтобы прислушиваться к любым изменениям. Как это может случиться? Это из собственной документации React:

shouldComponentUpdate()вызывается перед рендерингом при получении нового реквизита или состояния.По умолчанию true.

По умолчанию это правда! Что нормально. Реакция сделана таким образом.

Тогда как мы можем сделать это лучше?

Вынос 2: используйтеchildrenпроп

Хорошо. Единственная разница в том, чтомы используем детскую опору,

И результат?

Этобольше не рендерит дочерние компоненты! Я до сих пор не понял, почему именно Reactможетв этом коде, ноне могутв предыдущем коде, но это то, что есть.

Еда на вынос 3: использоватьReact.memo

memoбуквально запоминает функциональную составляющую, получая определеннуюpropsи если реквизиты останутся прежними, он просто вернет ранее визуализированный (такой же) компонент. Результат? То же, что и в решении 1. Он не выполняет повторную визуализациюChild1а такжеChild2, Так что я просто скопирую и вставлю gif сверху для тех, кому лень читать то, что написано, как и я:

Хорошо. Но если вы используете передачу вложенных объектов в качестве реквизита, вы должны реализовать свой собственныйareEqualфункция дляmemo, так:

Да. Я знаю, что проверять равенство вложенных объектов - это стиль старой школы, но он работает хорошо. На практике,вы используете что-то вродеisEqualотreact-fast-compareэто будет обрабатывать сравнение вложенных объектов для вас.

Хорошо. Теперь, каков результат?

Child1больше не знаетobjопора остается прежней, потому чтоmemoработает только поверхностное сравнениеprevPropsа такжеnextPropsНоChild2точно знает, используяareEqualфункция, котораяobjна самом деле остается неизменным, и, следовательно, он не нуждается в повторной визуализации.

Вынос 4: использованиеPureComponent/shouldComponentUpdate

Конечно, вы можете использовать свой компонент класса. Вот и краткое объяснение этого:

Выше приведена точная копия реализаций функциональных компонентов. Вот подробности:

  1. Ты используешьPureComponentдляChild1, но это не будет служить своей цели, так же, какmemoпотому что он имеет вложенный объект в качестве своей опоры. Он будет продолжать повторную визуализацию после повторной визуализации родительского компонента. Это работало бы, если бы не было вложенных свойств объекта.
  2. Ты используешьshouldComponentUpdateдляChild2и это будет работать. Он сравнивает вложенные объекты так же, какareEqual, В случае, если естьstate, вы можете использоватьnextStateсравнить сthis.state(предыдущее состояние).
  3. Это так раздражает, писать много шаблонов кодов дляclassсоставная часть. Вот почему я предпочитаю функциональные компоненты ... (да, я немного отвлекся)

Конечно, результат совпадает с последним фрагментом кода. Давайте снова скопируем и вставим тот же самый гиф для людей:

Вывод 5: разобраться с функциями из реквизита при сравнении

Во многих случаях реквизиты - это функции, особенно когда вы подключаете свои функции к компонентам с помощью Redux.mapDispatchToPropsили передать свои функции в качестве подпорки от родителей к детям, чтобы получать уведомления о том, что произошло с детьми.

Работа с функциями может показаться очень простой вещью, но она может серьезно повредить производительности вашего приложения, если вы этого не сделаете.

Мы видим много таких шаблонов:

Детали:

  1. Сейчасobjопора больше не объект. Это просто сталоnum,
  2. Вы передаете функцию под названиемsayHiInChildrenвChild2какprop,
  3. Вы мелко сравниваете, если все реквизиты одинаковы, оба вChild1а такжеChild2хотя и разными способами (Child1не использует кастомareEqualфункция, ноChild2является. Но по сути они делают то же самое в этом контексте).
  4. Все будет перерисовывать, хотя реквизитChild1а такжеChild2остаются точно такими же. Посмотрите на результат:

Но почему!? Что ж..

В javascript вряд ли есть способ сравнить функции

Это всегда будет возвращать false, потому что функции - это объекты в javascript, то есть они сравниваются не со значениями, а со ссылками:

Единственный способ сделать это возможно, это иметь ту же ссылку:

Тогда как мы можем иметь дело с функциями в реквизит? Ну, есть два пути:

  1. Исключить их
  2. Стригируйте их

1. Исключить их

Вы можете использовать lodash_.isFunctionпроверить, является ли что-то функцией. Тогда у вас есть только свойства объекта, которые не являются функциями, чтобы применить это кareEqualфункция.

Теперь вы можете сделать это:

Результат? Только родительский компонент перерисовывает. По сути, вы сравниваете только реквизиты, которые не являются функциями.

2. Стригируйте их

По умолчанию,JSON.stringifyне поддерживает строковые функции. Таким образом, вы должны использовать сторонние библиотеки, такие какjsonfn,

Давай сделаем это:

JSONfn.stringifyсгенерирует строку, похожую на:

Использование этой функции даст вам тот же результат, что и в предыдущем примере. Вот еще одна копия и вставка:

Yeap. Вот и все.

Резюме

  • Если вы не используетеchildrenопора, каждый компонент внутриrenderФункция родителя будет перерисована по умолчанию.
  • Есть способы предотвратить бесполезные повторные рендеры: (1) ИспользованиеmemoсareEqualили (2) ИспользоватьPureComponentилиshouldComponentUpdate
  • Если в вашем реквизите есть функции, перед использованиемareEqualилиshouldComponentUpdate: (1) исключить их или (2) зачеркнуть их

Первоначально опубликовано наhttps://9oelm.github.io/2019-10-02--Making-stupid-react-smart-in-re-rendering/,

редактировать

Прежде всего, спасибо за проницательные комментарии.

  1. Стоимость строковых функций может быть больше, чем выгода от самой оптимизации. Так что не используйтеstringify, действительно.
  2. Вы должны вместо этого использоватьuseCallbackв вашем функциональном компоненте (см. раздел комментариев для примера). Но, тем не менее, я не уверен, как мы могли бы сделать это дляclassсоставная часть. У кого-нибудь есть хорошая идея?

Оригинальная статья

Footer decor

© machinelearningmastery.ru | Ссылки на оригиналы и авторов сохранены. | map