React представляет новое API (context API), которое использует "паттерн" (шаблон) render props (подробнее). На семинарах, встречах и в твиттере я вижу, что возникает много вопросов об использовании render props вне рендера, например, в обработчиках событий или "хуках" жизненного цикла(`lifecycle hooks').

Полтора года назад, когда я работал над React Router v4, меня особенно заинтересовало, как раз и навсегда решить проблему "глубоких обновлений". Я создал библиотеку с названием react-context-emission (позднее — react-broadcast) с API концептуально идентичным тому, что представил React в своем новом context API.
// React context emission API const { LocationEmitter, LocationSubscriber } = createContextEmission('location') <LocationEmitter location={value}/> <LocationSubscriber>{({ location }) => (...)}</LocationSubscriber> // Новое React Context API const { Provider, Consumer } = React.createContext() <Provider value={location}/> <Consumer>{value => (...)}</Consumer>
После использования этого шаблона мне действительно понравилось связывать значения переменных с компонентами через контекст и отрисовывать свойства (рендерить props), однако я изо всех сил старался получить доступ к контекстным значениям за пределами рендеринга. В моей реализации я привык получать их из this.context повсеместно. Это было одной из причин, по которой мы вернулись к использованию текущего (устаревшего?) contextTypes API в React Router.
Решить эту проблему не так сложно. Чтобы понять это, мне потребовалось время. Однако, как только вы увидите решение, оно покажется вам очевидным. Убедитесь сами!
Доступ к значениям в обработчиках событий
Нужно лишь… передать значение в обработчик:
class Something extends React.Component { handleClick = (event, stuff) => { console.log(stuff); }; render() { return ( <SomeContext.Consumer> {stuff => ( <div> <h1>Cool! {stuff}</h1> <button onClick={event => this.handleClick(event, stuff)}> Click me </button> </div> )} </SomeContext.Consumer> ); } }
Доступ к значениям в lifecycle hooks
В случае с lifecycle hooks предыдущий шаблон не работает, т.к. не мы вызываем хуки, а React. Предлагаю три шаблона, которые я использовал, выбирайте, который больше понравится (третий — мой любимый!).
Оборачиваем
Вы можете получить доступ к данным путем создания двух компонентов: контейнера, который использует контекст, и контейнера, который принимает контекст как свойство.
// Поглотите его (имеется, в виду компонент) const SomethingContainer = () => ( <SomeContext.Consumer> {stuff => <Something stuff={stuff} />} </SomeContext.Consumer> ); // Вуаля, получите stuff в prop! Context в ваших методах жизненного цикла class Something extends React.Component { componentDidMount() { console.log(this.props.stuff); } render() { return ( <div> <h1>Cool! {this.props.stuff}</h1> </div> ); } }
Создаем компоненты высшего порядка (HOC-компоненты)
Возможно, вы уже привыкли декорировать одни компоненты другими (например, с помощью HOC — High Order Component). Можно довольно быстро превратить render prop компонент в компонент высшего порядка. Я не особо люблю такой способ, поскольку для реализации он требует значительные перетасовки в коде и множество концепций.
// HOC const withStuff = Comp => props => ( <SomeContext.Consumer> {stuff => <Comp stuff={stuff} />} </SomeContext.Consumer> ); // Декорированный класс class SomethingImpl extends React.Component { componentDidMount() { console.log(this.props.stuff); } render() { return ( <div> <h1>Cool! {this.props.stuff}</h1> </div> ); } } // the actual decoration const Something = withStuff(SomethingImpl)
Компонентный компонент: моя новая любовь.
Однажды я создал компонент, который просто брал функцию как свойство и вызывал функцию в componentDidUpdate. Я уже делал свойство с именем render и теперь у меня появилось еще одно под названием didUpdate. Я понял, что можно преобразовать каждый метод класса компонента в свойство компонента, и вот так появился @reactions/component!
Очень удобно компоновать render props в хуках жизненного цикла без всяких перетасовок в коде:
import Component from '@reactions/component'; const Something = () => ( <SomeContext.Consumer> {stuff => ( <Component didMount={() => console.log(stuff)}> <h1>Cool! {stuff}</h1> </Component> )} </SomeContext.Consumer> );
Обычно свойства можно сравнить в componentDidUpdate, это тоже неплохо работает:
const Something = () => ( <SomeContext.Consumer> {stuff => ( <Component stuff={stuff} didUpdate={({prevProps, props}) => { console.log(prevProps.stuff === props.stuff); }} > <h1>Cool! {stuff}</h1> </Component> )} </SomeContext.Consumer> );
Это мой любимый метод. Код может изменяться и перемещаться без каких-либо несоответствий, поскольку он не несет новых концепций, вся структура организуется компонентами. Если вы больше не нуждаетесь в методах жизненных циклов, вам не надо распутывать абстракцию, просто удалите <Component/>.
Итак, теперь у вас есть все необходимое. Все очевидно, стоит лишь раз увидеть, а ведь до этого все казалось сложным.
