Реагировать функциональный компонент без состояния, PureComponent, Component; Какие различия и когда мы должны использовать что?

189

Мы узнали, что из React v15.3.0 у нас есть новый базовый класс под названием PureComponent, который можно расширять с помощью встроенного PureRenderMixin . Что я понимаю, так это то, что под капотом используется неглубокое сравнение реквизита внутри shouldComponentUpdate.

Теперь у нас есть 3 способа определить компонент React:

  1. Функциональный компонент без сохранения состояния, который не расширяет класс
  2. Компонент, расширяющий PureComponentкласс
  3. Нормальный компонент, расширяющий Componentкласс

Некоторое время назад мы называли компоненты без состояний чистыми компонентами или даже тупыми компонентами. Похоже, что полное определение слова «чистый» теперь изменилось в React.

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


Обновление :

Вот вопрос, который я ожидаю прояснить:

  • Должен ли я определить мои простые компоненты как функциональные (для простоты) или расширить PureComponentкласс (для производительности)?
  • Является ли повышение производительности реальным компромиссом для простоты, которую я потерял?
  • Нужно ли мне когда-нибудь расширять нормальный Componentкласс, когда я всегда могу использовать PureComponentдля лучшей производительности?
Ядху Киран
источник

Ответы:

315

Как вы решите, как вы выбираете между этими тремя в зависимости от назначения / размера / реквизита / поведения наших компонентов?

Расширение из React.PureComponentили React.Componentс помощью пользовательского shouldComponentUpdateметода влияет на производительность. Использование функциональных компонентов без сохранения состояния является «архитектурным» выбором и не дает никаких преимуществ с точки зрения производительности (пока).

  • Для простых компонентов, предназначенных только для презентаций, которые необходимо легко использовать повторно, предпочитайте функциональные компоненты без сохранения состояния. Таким образом, вы уверены, что они не связаны с реальной логикой приложения, что их очень легко протестировать и у них нет неожиданных побочных эффектов. Исключение составляют случаи, когда по какой-то причине их много или вам действительно нужно оптимизировать их метод рендеринга (поскольку вы не можете определить shouldComponentUpdateфункциональный компонент без состояния).

  • Расширьте, PureComponentесли вы знаете, что ваш вывод зависит от простых реквизитов / состояния («простой», что означает отсутствие вложенных структур данных, так как PureComponent выполняет поверхностное сравнение) И вам нужно / можно получить некоторые улучшения производительности.

  • Расширьте Componentи внедрите свой собственный, shouldComponentUpdateесли вам нужно некоторое повышение производительности, выполняя пользовательскую логику сравнения между следующим / текущим реквизитом и состоянием. Например, вы можете быстро выполнить глубокое сравнение, используя lodash # isEqual:

    class MyComponent extends Component {
        shouldComponentUpdate (nextProps, nextState) {
            return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
        }
    }

Кроме того, реализация вашей собственной shouldComponentUpdateили выход из нее PureComponentявляется оптимизацией, и, как обычно, вы должны начать изучать ее, только если у вас есть проблемы с производительностью ( избегайте преждевременной оптимизации ). Как правило, я всегда стараюсь выполнять эти оптимизации после того, как приложение находится в рабочем состоянии, причем большинство функций уже реализовано. Намного легче сосредоточиться на проблемах производительности, когда они действительно мешают.

Подробнее

Функциональные компоненты без состояния:

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

Плюсы:

  • Простейший способ определения компонента в React. Если вам не нужно управлять каким-либо состоянием, зачем беспокоиться о классах и наследовании? Одно из основных различий между функцией и классом заключается в том, что с функцией вы уверены, что вывод зависит только от ввода (а не от истории предыдущих выполнений).

  • В идеале в вашем приложении вы должны стремиться иметь как можно больше компонентов без сохранения состояния, поскольку это обычно означает, что вы переместили свою логику за пределы слоя представления и переместили ее в нечто вроде избыточности, что означает, что вы можете проверить свою настоящую логику без необходимости что-либо визуализировать. (гораздо проще тестировать, больше использовать повторно и т. д.).

Минусы:

  • Нет методов жизненного цикла. У вас нет возможности определить componentDidMountи других друзей. Обычно вы делаете это в родительском компоненте, находящемся выше в иерархии, чтобы вы могли превратить всех потомков в детей без состояния.

  • Нет способа вручную контролировать, когда требуется повторная визуализация, поскольку вы не можете определить shouldComponentUpdate. Повторный рендеринг происходит каждый раз, когда компонент получает новые реквизиты (нет способа поверхностного сравнения и т. Д.). В будущем React может автоматически оптимизировать компоненты без сохранения состояния, а сейчас есть некоторые библиотеки, которые вы можете использовать. Поскольку компоненты без состояния - это просто функции, в основном это классическая проблема «запоминания функций».

  • Ссылки не поддерживаются: https://github.com/facebook/react/issues/4936

Компонент, который расширяет класс PureComponent VS Нормальный компонент, который расширяет класс Component:

React имел обыкновение иметь, PureRenderMixinвы могли присоединиться к классу, определенному с использованием React.createClassсинтаксиса. Миксин будет просто определять shouldComponentUpdateвыполнение поверхностного сравнения между следующим реквизитом и следующим состоянием, чтобы проверить, изменилось ли что-нибудь там. Если ничего не меняется, то нет необходимости выполнять повторную визуализацию.

Если вы хотите использовать синтаксис ES6, вы не можете использовать миксины. Поэтому для удобства React представил PureComponentкласс, от которого вы можете наследовать, а не использовать Component. PureComponentпросто реализует shouldComponentUpdateтаким же образом PureRendererMixin. Это в основном удобство, поэтому вам не нужно реализовывать это самостоятельно, поскольку поверхностное сравнение между текущим / следующим состоянием и реквизитом, вероятно, является наиболее распространенным сценарием, который может дать вам быстрый выигрыш в производительности.

Пример:

class UserAvatar extends Component {
    render() {
       return <div><img src={this.props.imageUrl} /> {{ this.props.username }} </div>
    }
} 

Как видите, результат зависит от props.imageUrlи props.username. Если в родительском компоненте вы выполняете рендеринг <UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />с одинаковыми реквизитами, React будет вызывать renderкаждый раз, даже если результат будет точно таким же. Однако помните, что React реализует dom diffing, поэтому DOM фактически не будет обновляться. Тем не менее, выполнение DOM Diffing может быть дорогостоящим, поэтому в этом случае это будет пустая трата времени.

Если UserAvatarкомпонент расширяется PureComponent, выполняется поверхностное сравнение. И потому, что реквизиты и nextProps одинаковы, renderне будет вызываться вообще.

Примечания к определению «чистый» в React:

В общем, «чистая функция» - это функция, которая всегда оценивает один и тот же результат при одинаковых входных данных. Вывод (для React, это то, что возвращается renderметодом) не зависит от какой-либо истории / состояния и не имеет побочных эффектов (операций, которые изменяют «мир» вне функции).

В React компоненты без сохранения состояния не обязательно являются чистыми компонентами в соответствии с приведенным выше определением, если вы называете компонентом без состояния компонент, который никогда не вызывает this.setStateи не использует this.state.

Фактически, PureComponentвы все равно можете выполнять побочные эффекты во время методов жизненного цикла. Например, вы можете отправить ajax-запрос внутрь componentDidMountили выполнить некоторое вычисление DOM, чтобы динамически настроить высоту div внутри render.

Определение «тупых компонентов» имеет более «практическое» значение (по крайней мере, в моем понимании): тупому компоненту «говорят», что делать с родительским компонентом через реквизиты, и он не знает, как это делать, но использует реквизиты. обратные вызовы вместо.

Пример «умного» AvatarComponent:

class AvatarComponent extends Component {
    expandAvatar () {
        this.setState({ loading: true });
        sendAjaxRequest(...).then(() => {
            this.setState({ loading: false });
        });
    }        

    render () {
        <div onClick={this.expandAvatar}>
            <img src={this.props.username} />
        </div>
    }
}

Пример "тупой" AvatarComponent:

class AvatarComponent extends Component {
    render () {
        <div onClick={this.props.onExpandAvatar}>
            {this.props.loading && <div className="spinner" />}
            <img src={this.props.username} />
        </div>
    }
}

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

fabio.sussetto
источник
1
Я очень ценю ваш ответ и знания, которыми вы поделились. Но мой настоящий вопрос: когда мы должны выбирать что? , Для того же примера, который вы упомянули в своем ответе, как мне его определить? Должен ли он быть функциональным компонентом без сохранения состояния (если так, почему?), Или расширением PureComponent (почему?), Или расширением класса Component (опять же, почему?). Как вы решите, как вы выбираете между этими тремя в зависимости от назначения / размера / реквизита / поведения наших компонентов?
Ядху Киран
1
Нет проблем. Для функционального компонента без сохранения состояния есть список плюсов / минусов, который я могу рассмотреть, чтобы решить, будет ли он подходящим. Это отвечает на первый вопрос? Я попытаюсь ответить на вопрос о выборе немного больше.
fabio.sussetto
2
Функциональные компоненты всегда перерисовываются при обновлении родительского компонента, даже если они вообще не используются props. пример .
AlexM
1
Это один из самых полных ответов, которые я прочитал за последнее время. Отличная работа. Один комментарий о самом первом предложении: при расширении PureComponentне следует реализовывать shouldComponentUpdate(). Вы должны увидеть предупреждение, если вы делаете это на самом деле.
jjramos
1
Для реального увеличения производительности вы должны попытаться использовать PureComponentкомпоненты, которые имеют вложенные свойства объекта / массива. Конечно, вы должны осознавать, что происходит. Если я правильно понимаю, если вы не изменяете реквизиты / состояние напрямую (что React пытается помешать вам делать с предупреждениями) или через внешнюю библиотеку, тогда вы должны нормально использовать PureComponentвместо Componentпочти везде ... за исключением очень простых компонентов, где на самом деле это может быть быстрее, НЕ использовать его - см. news.ycombinator.com/item?id=14418576
Мэтт Браун
28

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

  1. Компонент без сохранения состояния - это компонент, у которого нет жизненного цикла, поэтому эти компоненты следует использовать при отображении элемента повторения родительского компонента, например при отображении текстового списка, который просто отображает информацию и не имеет каких-либо действий для выполнения.

  2. Чистый компонент - это элементы, которые имеют жизненный цикл, и они всегда будут возвращать один и тот же результат, когда дается определенный набор реквизита. Эти компоненты могут использоваться при отображении списка результатов или данных конкретного объекта, которые не имеют сложных дочерних элементов и используются для выполнения операций, которые влияют только на себя. такое отображение списка пользовательских карточек или списка карточек продуктов (основная информация о товаре), и единственное действие, которое может выполнить пользователь, - это щелкнуть мышью, чтобы просмотреть подробную страницу или добавить в корзину.

  3. Нормальные компоненты или сложные компоненты - я использовал термин сложный компонент, потому что они обычно являются компонентами уровня страницы и состоят из множества дочерних компонентов, и поскольку каждый из дочерних элементов может вести себя по-своему, поэтому вы не можете быть на 100% уверены, что он будет оказать тот же результат в данном состоянии. Как я уже сказал, обычно их следует использовать в качестве компонентов контейнера.

abhirathore2006
источник
1
Этот подход может сработать, но вы можете упустить большой прирост производительности. Использование PureComponentкомпонентов корневого уровня и компонентов в верхней части иерархии, как правило, дает вам наибольшее повышение производительности. Конечно, вам нужно избегать мутирующих подпорок и указывать напрямую, чтобы чистые компоненты работали правильно, но мутирующие объекты напрямую в любом случае являются антишаблоном в React.
Мэтт Браун
5
  • React.Componentявляется стандартным компонентом по умолчанию. Вы объявляете их, используя classключевое слово и extends React.Component. Думайте о них как о классе, с методами жизненных циклов, обработчиками событий и другими методами.

  • React.PureComponentэто то, React.Componentчто реализует shouldComponentUpdate()с функцией, которая делает поверхностное сравнение его propsи state. Вы должны использовать, forceUpdate()если вы знаете, что у компонента есть реквизиты или состояние вложенных данных, которые изменились, и вы хотите выполнить повторную визуализацию. Так что они не очень хороши, если вам нужны компоненты для повторного рендеринга, когда массивы или объекты, которые вы передаете как реквизиты или задаете в своем состоянии, меняются.

  • Функциональные компоненты - это те, которые не имеют функций жизненного цикла. Они предположительно не имеют состояния, но они настолько хороши и чисты, что теперь у нас есть хуки (начиная с Реакта 16.8), так что у вас все еще может быть состояние. Так что я думаю, что они просто "чистые компоненты".

JackyJohnson
источник