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


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


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 = 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)

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

Поделиться с друзьями
-->

Комментарии (16)


  1. FasSsko
    16.04.2017 20:18

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


  1. justboris
    16.04.2017 23:45
    +2

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


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


  1. n7olkachev
    17.04.2017 00:48

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


    1. comerc
      18.04.2017 15:01

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


      1. n7olkachev
        18.04.2017 20:13

        Без редакса берем и

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


        1. comerc
          18.04.2017 20:40

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


          1. justboris
            18.04.2017 20:53
            +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 есть и более интересные примеры.


          1. n7olkachev
            20.04.2017 18:34
            +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 })
            


            Думаю, основная идея ясна.


  1. Hydro
    17.04.2017 07:42
    +3

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


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

    Если например текстовый контрол хранит displayText в redux, то набор каждой буквы будет адово влиять на производительность и лагать на слабых машинах, особенно в IE, в таком случае надо все равно коннектится на стейт и юзать функциональные компоненты?


    1. PQR
      17.04.2017 11:18
      +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%.


      1. comerc
        18.04.2017 14:58

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


  1. VolCh
    17.04.2017 11:24
    +2

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

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


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

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


    1. sp3ber
      17.04.2017 17:59
      +1

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

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


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


  1. zorro1211
    23.04.2017 20:43
    +1

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


    1. comerc
      25.04.2017 16:11

      не нужно конвертировать если нужны life-cycle методы

      Пожалуйста!