Как говорится, в редакцию пришло письмо: "не могли бы вы подробно разъяснить..." Отвечаю публично, кому оно надо, а применение можно пощупать тут.
Сначала были компоненты на классах:
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)
justboris
16.04.2017 23:45+2Раз уж тут речь идет о функциональных компонентах, полезным будет упомянуть библиотеки recompose и recompact.
В них уже есть много полезных утилит для функциональных компонентов, в том числе и pureComponent, приведенный в статье.
n7olkachev
17.04.2017 00:48Можно через HOC и statefull функциональные компоненты делать.
comerc
18.04.2017 15:01Да, только не вижу профита.
n7olkachev
18.04.2017 20:13Без редакса берем и
Выкидываем: объявление класса, constructor(), render(), const для деструктуризации props, this.
comerc
18.04.2017 20:40Покажите пример. Я себе плохо не представляю.
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 есть и более интересные примеры.
n7olkachev
20.04.2017 18:34+2const 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 })
Думаю, основная идея ясна.
Hydro
17.04.2017 07:42+3Ещё желательно преодолеть коннектобоязнь — не тащить все свойства через родительские компоненты, а применять connect() для дочерних компонентов по мере использования свойств
State компонента в Redux VS локальный state компонента — какова разница по производительности?
Redux state может быть тяжелым и очень ветвистым с туевой хучей редьюссеров и десятком middleware, каждый dispatch(action) будет прогоняться через всю эту мясорубку.
Если например текстовый контрол хранит displayText в redux, то набор каждой буквы будет адово влиять на производительность и лагать на слабых машинах, особенно в IE, в таком случае надо все равно коннектится на стейт и юзать функциональные компоненты?PQR
17.04.2017 11:18+4State компонента в Redux VS локальный state компонента — какова разница по производительности?
На этот вопрос отвечают разработчики мобильной веб-версии Twitter в своём недавнем посте: https://medium.com/@paularmstrong/twitter-lite-and-high-performance-react-progressive-web-apps-at-scale-d28a00e780a3 (см. секцию «Optimizing Redux»).
Если кратко: отказавшись от состояния в Redux в некоторых местах в пользу локального состояния удалось получить прирост производительности 50%.comerc
18.04.2017 14:58Так значит я на правильном пути! :) Сначала быстрая разработка, потом оптимизация.
VolCh
17.04.2017 11:24+2или перенести всё состояние в стор redux-а (как единственный источник правды). Локальные состояния компонентов — это дополнительный уровень абстракции, требующий обслуживания. А зачем?
Глобальный стор redux-а со всем его бойлерплейтом — дополнительный уровень абстракции (причём далеко не элементарный) для хранения и управления локальным состоянием.
Ещё желательно преодолеть коннектобоязнь — не тащить все свойства через родительские компоненты, а применять connect() для дочерних компонентов по мере использования свойств.
А если это не "коннектобоязнь", а желание максимально снизить зависимость от redux, чтобы его замену на что-то другое произошла максимально безболезненно?
sp3ber
17.04.2017 17:59+1Ещё желательно преодолеть коннектобоязнь — не тащить все свойства через родительские компоненты, а применять connect() для дочерних компонентов по мере использования свойств.
А если это не «коннектобоязнь», а желание максимально снизить зависимость от redux, чтобы его замену на что-то другое произошла максимально безболезненно?
Если работать в парадигме «умных» и «глупых» компонентов, то ваши коннекторы просто заменятся на другие «умные» компоненты в mobX и проч. Это всегда лучше, чем прокидывать миллиард пропсов из верхнеуровнего компонента (SomePage и проч.) — это всегда превращается в {...this.props} на каждый дочерний компонент и потом сложно отследить и отладить, откуда все взялось, плюс лишние пропсы попадают.
Коннектор это просто обертка, чаще всего ему и разметка не нужна:
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyDumbCompoent)
zorro1211
23.04.2017 20:43+1Для себя пока решил использовать напрямую классы с PureComponent, вместо обертки — не нужно конвертировать если нужны life-cycle методы, мой редактор (WebStorm) хорошо работает с классами (auto-complete), но не с функциональными компонентами. Хотя конечно синтаксис раздут по сравнению с фунциональными компонентами, что можно вылечить снипетами, но лучше я дождусь оптимизаций со стороны реакта.
FasSsko
Забивать редакс всяким мусором не лучшая идея…