Функциональные компоненты

  • Tutorial

Как говорится, в редакцию пришло письмо: "не могли бы вы подробно разъяснить..." Отвечаю публично, кому оно надо, а применение можно пощупать тут.


Сначала были компоненты на классах:


class Square extends React.Component {
  constructor() {
    super()
    this.state = {
      value: null,
    }
  }
  render() {
    const { value, onClick } = this.props
    return (
      <button className="square" onClick={onClick}>
        {value}
      </button>
    )
  }
}

Потом явились функциональные компоненты:


const Square = ({ value, onClick }) => {(
  <button className="square" onClick={onClick}>
    {value}
  </button>
)}

В чём разница? Выкидываем: объявление класса, constructor(), render(), const для деструктуризации props, this. А ещё исчезло состояние компонента — мы получаем stateless functional components.


Как дальше жить без локального состояния: 1) или применять функциональные компоненты только там, где не нужно хранить состояние; 2) или перенести всё состояние в стор redux-а (как единственный источник правды). Локальные состояния компонентов — это дополнительный уровень абстракции, требующий обслуживания. А зачем?


Ещё желательно преодолеть коннектобоязнь — не тащить все свойства через родительские компоненты, а применять connect() для дочерних компонентов по мере использования свойств.


Разобрались, а как же применить PureComponent к функциональным компонентам? На помощь приходит техника Higher-Order Components:


// utils.js
import React from 'react'

export const pureComponent = (fn) => {
  class Wrapper extends React.PureComponent {
    render() {
      return fn(this.props, this.context)
    }
  }
  // не надо, т.к. подписывает на контекст как и функциональный компонент,
  // так и оболочку-PureComponent; лучше назначать сразу оболочке (снаружи)
  // Wrapper.contextTypes = fn.contextTypes
  Wrapper.displayName = `PureComponent(${fn.name})`
  return Wrapper
}

Присваивание displayName — для красоты в React DevTools. Подробнее про contextTypes можно почитать тут.


Пример использования:


import { pureComponent } from 'utils'

const Square = ({ value, onClick }) => {(
  <button className="square" onClick={onClick}>
    {value}
  </button>
)}

export default pureComponent(Square)

Рекомендуемые статьи

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 16

    0
    Забивать редакс всяким мусором не лучшая идея…
      +2

      Раз уж тут речь идет о функциональных компонентах, полезным будет упомянуть библиотеки recompose и recompact.


      В них уже есть много полезных утилит для функциональных компонентов, в том числе и pureComponent, приведенный в статье.

        0
        Можно через HOC и statefull функциональные компоненты делать.
          0

          Да, только не вижу профита.

            0
            Без редакса берем и
            Выкидываем: объявление класса, constructor(), render(), const для деструктуризации props, this.
              0

              Покажите пример. Я себе плохо не представляю.

                +1

                Может быть, речь об этом?


                import {withState} from 'recompose';
                
                const Counter = ({ counter, setCounter }) =>
                  <div>
                    Count: {counter}
                    <button onClick={() => setCounter(n => n + 1)}>Increment</button>
                    <button onClick={() => setCounter(n => n - 1)}>Decrement</button>
                  </div>
                )(Counter);
                
                export default withState(
                  'counter', // имя значения
                  'setCounter', // имя коллбека для его обновления
                  0 // начальное значение
                )

                В документации recompose есть и более интересные примеры.

                  +2
                  const withState = (f, defaultState = {}) => class extends Component {
                      render () {
                          return f({ 
                              state: this.state || defaultState, 
                              setState: this.setState.bind(this) 
                          })
                      }
                  }
                  
                  const Counter = withState(({ state, setState }) => (
                      <span>
                          <button onClick={() => setState({ count : state.count - 1 })}>
                              -
                          </button>
                          {state.count}
                          <button onClick={() => setState({ count : state.count + 1 })}>
                              +
                          </button>
                      </span>
                  ), { count : 0 })
                  


                  Думаю, основная идея ясна.
            +3
            Ещё желательно преодолеть коннектобоязнь — не тащить все свойства через родительские компоненты, а применять connect() для дочерних компонентов по мере использования свойств


            State компонента в Redux VS локальный state компонента — какова разница по производительности?
            Redux state может быть тяжелым и очень ветвистым с туевой хучей редьюссеров и десятком middleware, каждый dispatch(action) будет прогоняться через всю эту мясорубку.

            Если например текстовый контрол хранит displayText в redux, то набор каждой буквы будет адово влиять на производительность и лагать на слабых машинах, особенно в IE, в таком случае надо все равно коннектится на стейт и юзать функциональные компоненты?
              +4
              State компонента в Redux VS локальный state компонента — какова разница по производительности?
              На этот вопрос отвечают разработчики мобильной веб-версии Twitter в своём недавнем посте: https://medium.com/@paularmstrong/twitter-lite-and-high-performance-react-progressive-web-apps-at-scale-d28a00e780a3 (см. секцию «Optimizing Redux»).

              Если кратко: отказавшись от состояния в Redux в некоторых местах в пользу локального состояния удалось получить прирост производительности 50%.
                0

                Так значит я на правильном пути! :) Сначала быстрая разработка, потом оптимизация.

              +2
              или перенести всё состояние в стор redux-а (как единственный источник правды). Локальные состояния компонентов — это дополнительный уровень абстракции, требующий обслуживания. А зачем?

              Глобальный стор redux-а со всем его бойлерплейтом — дополнительный уровень абстракции (причём далеко не элементарный) для хранения и управления локальным состоянием.


              Ещё желательно преодолеть коннектобоязнь — не тащить все свойства через родительские компоненты, а применять connect() для дочерних компонентов по мере использования свойств.

              А если это не "коннектобоязнь", а желание максимально снизить зависимость от redux, чтобы его замену на что-то другое произошла максимально безболезненно?

                +1
                Ещё желательно преодолеть коннектобоязнь — не тащить все свойства через родительские компоненты, а применять connect() для дочерних компонентов по мере использования свойств.

                А если это не «коннектобоязнь», а желание максимально снизить зависимость от redux, чтобы его замену на что-то другое произошла максимально безболезненно?


                Если работать в парадигме «умных» и «глупых» компонентов, то ваши коннекторы просто заменятся на другие «умные» компоненты в mobX и проч. Это всегда лучше, чем прокидывать миллиард пропсов из верхнеуровнего компонента (SomePage и проч.) — это всегда превращается в {...this.props} на каждый дочерний компонент и потом сложно отследить и отладить, откуда все взялось, плюс лишние пропсы попадают.
                Коннектор это просто обертка, чаще всего ему и разметка не нужна:
                export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyDumbCompoent)
                
                +1

                Для себя пока решил использовать напрямую классы с PureComponent, вместо обертки — не нужно конвертировать если нужны life-cycle методы, мой редактор (WebStorm) хорошо работает с классами (auto-complete), но не с функциональными компонентами. Хотя конечно синтаксис раздут по сравнению с фунциональными компонентами, что можно вылечить снипетами, но лучше я дождусь оптимизаций со стороны реакта.

              Only users with full accounts can post comments. Log in, please.