Главная » Статьи » Работа с Context в React

Работа с Context в React

Работа с Context в React

От автора: React Context в настоящее время является экспериментальным API — но скоро он станет полноправным инструментом разработки! Есть много причин, по которым он представляет интерес, но, возможно, основные заключаются в том, что Context позволяет родительским компонентам неявно передавать данные своим потомкам, независимо от вложенности компонентов. Другими словами, данные могут быть добавлены к родительскому компоненту, а затем любой потомок может к ним подключиться.

Хотя это часто используется для чего-то вроде Redux, неплохо задействовать это, если нам не требуется сложное управление данными. Подумай об этом! Мы создаем пользовательский нисходящий поток данных, решая, какие реквизиты передаются и на какие уровни. Довольно круто.

Context отлично подходит для тех областей, где у вас есть много компонентов, которые зависят от одного фрагмента данных, но находятся глубоко внутри дерева иерархии. Явно передавать данные каждому отдельному компоненту часто может быть слишком трудоемким, и здесь гораздо проще использовать Context.

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

class Parent extends React.Component { render(){ return <Child color="red" />; }
} class Child extends React.Component { render(){ return <GrandChild color={this.props.color} /> }
} class GrandChild extends React.Component { render(){ return ( <div style={{color: this.props.color}}> Yep, I'm the GrandChild </div> ); }
}

Что делать, если нам не нужно, чтобы компонент Child содержал свойство на первом месте? Контекст позволяет нам обойти дочерний компонент и передать цвет непосредственно от Parent к GrandChild:

class Parent extends React.Component { // Позволяем дочерним элементам использовать context getChildContext() { return { color: 'red' }; } render(){ return <Child />; }
} Parent.childContextTypes = { color: PropTypes.string
}; class Child extends React.Component { render() { // Свойство удаляется и context переходит к GrandChild return <GrandChild /> }
} class GrandChild extends React.Component { render() { return ( <div style={{color: this.context.color}}> Yep, I'm still the GrandChild </div> ); }
} // Отображаем цвет GrandChild
GrandChild.contextTypes = { color: PropTypes.string
};

Хотя нам редко нужно отображать тот же цвет, где-нибудь внизу дерева компонентов. Ну, иногда нужно…

Но есть некоторые проблемы

Но в жизни не всегда все идет гладко, и Context в его текущем виде не исключение. Есть несколько основных проблем, с которыми вы, скорее всего, столкнетесь, если будете использовать Context для всех случаев, кроме самых простых.
Context отлично подходит для первоначального рендеринга. Обновление контекста «на лету»? Не так хорошо. Общей проблемой является то, что изменения Context не всегда отражаются в компоненте. Давайте разберем это более подробно.

Проблема 1: Использование PureComponent

Context трудно использовать с PureComponent, поскольку по умолчанию он не выполняет никаких по умолчанию с context. Shallow diffing, в отличие от PureComponent, проверяет, являются ли значения объекта строго равными. Если это не так, тогда (и только тогда) компонента будет обновляться. Но поскольку Context не задается явно, ну … ничего не происходит.

Проблема 2: Должен ли компонент обновляться? Возможно.

Context не обновляет компонента, если mustComponentUpdate компонента возвращает false. Если у вас есть собственный метод toComponentUpdate, то вам также необходимо учитывать Context. Чтобы включить обновления с помощью Context, мы можем обновить каждый отдельный компонент с помощью пользовательского shouldComponentUpdate, который выглядит примерно так.

import shallowEqual from 'fbjs/lib/shallowEqual'; class ComponentThatNeedsColorContext extends React.PureComponent { // nextContext отобразит цвет, как только мы применим ComponentThatNeedsColorContext.contextTypes // ПРИМЕЧАНИЕ: Выполнение следующего приведет к отображению ошибки в react v16.1.1 shouldComponentUpdate(nextProps, nextState, nextContext){ return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState) || !shallowEqual(this.context, nextContext); }
} ComponentThatNeedsColorContext.contextTypes = { color: PropTypes.string
};

Тем не менее, это не решает проблему промежуточного PureComponent между родительским и дочерним блоком обновления контекста. Это означает, что для каждого PureComponent между родителем и дочерним элементом должны быть определены contextTypes, и они также должны содержать метод toComponentUpdate. И на данный момент это требует очень большого объема работы, а выигрыш очень незначительный.

Подходы к решению проблем

К счастью, у нас есть несколько способов обойти эти проблемы.

Подход 1: Использование компонента более высокого порядка

Компонент более высокого порядка может читать из Context и передавать необходимые значения следующему компоненту в качестве свойства.

import React from 'react'; const withColor = (WrappedComponent) => { class ColorHOC extends React.Component { render() { const { color } = this.context; return <WrappedComponent style={{color: color}} {...this.props} /> } } ColorHOC.contextTypes = { color: React.PropTypes.string }; return ColorHOC;
}; export const Button = (props)=> <button {...props}>Button</button> // ColoredButton будет отображаться с любым цветом, которое задано в свойстве стиля context
export const ColoredButton = withColor( Button );

Подход 2: Использовать свойства Render

Свойства Render позволяют использовать свойства, чтобы передавать код между двумя компонентами.

class App extends React.Component { getChildContext() { return { color: 'red' } } render() { return <Button /> }
} App.childContextTypes = { color: React.PropTypes.string
} // Подключаем 'Color' в context 'App'
class Color extends React.Component { render() { return this.props.render(this.context.color); }
} Color.contextTypes = { color: React.PropTypes.string
} class Button extends React.Component { render() { return ( <button type="button"> {/* Return colored text within Button */} <Color render={ color => ( <Text color={color} text="Button Text" /> ) } /> </button> ) }
} class Text extends React.Component { render(){ return ( <span style={{color: this.props.color}}> {this.props.text} </span> ) }
} Text.propTypes = { text: React.PropTypes.string, color: React.PropTypes.string,
}

Подход 3: Включение зависимостей

Третий способ, с помощью которого мы можем обойти эти проблемы — использовать включение зависимостей для ограничения context API и разрешить компонентам подписываться на них при необходимости.

Новый Context

Новый способ использования context, который в настоящее время запланирован для следующего неосновного релиза React (16.3), будет более читаемым и удобными для записи без «проблем» предыдущих версий. Теперь у нас есть новый метод, называемый createContext, который определяет новый context и возвращает как Provider, так и Consumer.

Provider устанавливает context, к которому могут подключаться все подкомпоненты. Он подключен через Consumer, который использует свойство отображения. Первым аргументом этой функции является value, которое мы передали Provider. При обновлении значения в Provider, все Consumer будут обновляться, чтобы отразить новое значение.

В качестве дополнительного преимущества использования нового context нам больше не нужно использовать childContextTypes, getChildContext и contextTypes.

const ColorContext = React.createContext('color');
class ColorProvider extends React.Component { render(){ return ( <ColorContext.Provider value={'red'}> { this.props.children } </ColorContext.Provider> ) }
} class Parent extends React.Component { render(){ // Оборачиваем 'Child' в провайдер цвета return ( <ColorProvider> <Child /> </ColorProvider> ); }
} class Child extends React.Component { render(){ return <GrandChild /> }
} class GrandChild extends React.Component { render(){ // Получаем context и передаем цвет в атрибут style return ( <ColorContext.Consumer> {/* 'color' is the value from our Provider */} { color => ( <div style={{color: color}}> Yep, I'm still the GrandChild </div> ) } </ColorContext.Consumer> ); }
}

Разделяйте Contexts

Поскольку у нас есть больший контроль над тем, как мы отображаем context и каким компонентам разрешено его использовать, мы можем индивидуально обернуть компоненты в различный context, даже если они вложены в один компонент. Посмотрите, как в следующем примере, дважды используя LightProvider, мы можем задать для двух компонентов отдельный context.

Заключение

Context — это мощный API, но его также очень легко использовать неправильно. Кроме того, есть несколько предостережений относительно его использования, и вам может быть очень трудно определить, в чем проблема, когда что-то пойдет не так. В то время как компоненты более высокого порядка и включение зависимостей предлагают альтернативы для большинства случаев, Context может быть полезен в изолированных частях вашего кода.

Однако после выхода новой версии Context нам больше не нужно будет беспокоиться об этом. В ней будет устранена необходимость определять contextTypes для отдельных компонентов, что открывает возможности для определения новых повторно используемых Context.

Автор: Neal Fennimore

Источник: https://css-tricks.com/

Редакция: Команда webformyself.