Реагує функціональний компонент без стану, 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/isissue/4936

Компонент, що розширює клас PureComponent VS Нормальний компонент, який розширює клас компонентів:

React використовувався для того, щоб PureRenderMixinви могли приєднатись до класу, визначеного за допомогою React.createClassсинтаксису. Mixin просто визначить 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кожен раз, навіть якщо вихід буде абсолютно однаковим. Пам'ятайте, що реалізація реалізує dom-різницю, тому DOM фактично не оновлювався. І все-таки виконання дому різного може бути дорогим, тому в цьому сценарії було б марно.

Якщо UserAvatarкомпонент поширюється PureComponentнатомість, виконується неглибоке порівняння. А оскільки реквізит і nextProps однакові, renderвзагалі не будуть називатися.

Примітки щодо визначення поняття "чистий" в React:

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

У React компоненти без стану не обов'язково є чистими компонентами згідно з вищезазначеним визначенням, якщо ви називаєте "без громадянства" компонент, який ніколи не викликає this.setStateі не використовує this.state.

Насправді, у програмі PureComponentви все одно можете виконувати побічні ефекти під час методів життєвого циклу. Наприклад, ви можете надіслати запит ajax всередині componentDidMountабо ви можете виконати деякий обчислення DOM, щоб динамічно регулювати висоту діла всередині 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>
    }
}

Зрештою, я б сказав, що "німий", "без громадянства" та "чистий" - це зовсім різні поняття, які іноді можуть перетинатися, але не обов'язково, залежно від випадків використання.


1
Я дуже ціную вашу відповідь та знання, якими ви поділилися. Але моє справжнє питання - коли нам вибрати що? . У тому ж прикладі, який ви згадали у своїй відповіді, як я повинен це визначити? Це повинен бути функціональний компонент без стану (якщо так, чому?), Або розширення PureComponent (чому?) Або розширення класу компонентів (знову ж чому?). Як ви вирішите, як ви обираєте між цими трьома на основі мети / розміру / реквізиту / поведінки наших компонентів?
Ядху Кіран

1
Нема проблем. Щодо функціонального компонента без громадянства, існує список плюсів і мінусів, який, на вашу думку, ви можете вирішити, чи підходить це. Це відповідь вам спочатку вказує? Я спробую трохи більше розглянути питання щодо вибору.
fabio.sussetto

2
Функціональні компоненти завжди відтворюються, коли батьківський компонент оновлюється, навіть якщо він взагалі не використовується props. приклад .
AlexM

1
Це одна з найбільш вичерпних відповідей, яку я прочитав за досить довгий час. Чудова робота. Один коментар щодо самого першого речення: Розширюючи PureComponent, ви не повинні реалізовувати shouldComponentUpdate(). Ви повинні побачити попередження, якщо ви робите це насправді.
jjramos

1
Для реального підвищення продуктивності вам слід спробувати використовувати PureComponentдля компонентів, які DO мають вкладені властивості об'єкта / масиву. Звичайно, ви повинні знати про те, що відбувається. Якщо я правильно розумію, якщо ви не мутуєте реквізит / стан безпосередньо (що React намагається запобігти вам робити попередження) або через зовнішню бібліотеку, вам слід добре використовувати, PureComponentа не Componentмайже скрізь ... за винятком дуже простих компонентів, де насправді можна швидше НЕ використовувати його - див. news.ycombinator.com/item?id=14418576
Метт Браун

28

Я не геній над реагуванням, але, з мого розуміння, ми можемо використовувати кожен компонент у наступних ситуаціях

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

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

  3. Звичайні компоненти або складні компоненти - я використовував термін складний компонент, тому що зазвичай це компоненти рівня сторінки та складається з багатьох компонентів дітей, і оскільки кожен з дітей може вести себе по-своєму унікальним чином, тому ви не можете бути на 100% впевнені, що це буде надати такий же результат у заданому стані. Як я вже говорив, зазвичай їх слід використовувати як компоненти контейнерів


1
Такий підхід може спрацювати, але ви можете пропустити великі результативність. Застосування PureComponentкомпонентів та компонентів на рівні коренів у верхній частині вашої ієрархії, як правило, там, де ви побачите найбільші підвищення продуктивності. Звичайно, вам потрібно уникати мутуючого реквізиту та констатувати безпосередньо для того, щоб чисті компоненти працювали коректно, але мутуючі об'єкти безпосередньо є анти-шаблоном у React.
Метт Браун

5
  • React.Componentє стандартним "нормальним" компонентом. Ви оголошуєте їх за допомогою classключового слова та extends React.Component. Подумайте про них як про клас, з методами життєвих циклів, обробниками подій та будь-якими іншими методами.

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

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

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