Если вы тратите много времени на написание компонентов React, вы столкнетесь с компонентами более высокого порядка (HOC) и, в конце концов, захотите написать свои собственные. Зачем ты напишешь один? Отчасти потому, что они крутые, но главным образом потому, что представляют собой полезную абстракцию. Вот когда вы знаете, что хотите использовать или написать HOC: когда вы хотите абстрагироваться от поведения. Подобно тому, как обычный компонент абстрагирует разметку и стили, HOC позволяет вам разделять поведение между компонентами. Какими примерами поведения вы хотели бы поделиться? Внедренные зависимости (реквизиты), измененная или альтернативная разметка и общие функции жизненного цикла - это типы вещей, которые я использовал и написал HOC для совместного использования. Для этого я использую три шаблона, и у каждого из них есть свои предпосылки и варианты использования.
Перво-наперво
Но сначала правило, которое должны соблюдать все HOC: HOC должен потреблять один компонент и возвращать один компонент. Под этим я подразумеваю, что использование HOC всегда должно выглядеть так:
const EnhancedComponent = myHOC(Component);
// ...later used as <EnhancedComponent />
Если вашему HOC нужна конфигурация, например, почтенному connect
от Redux, тогда ваш HOC должен использовать конфигурацию (ы) и вернуть функцию, которая потребляет один компонент и возвращает один компонент. (Возможно, нам следует называть это более высоким HOC, HHOC.) Позвольте мне пояснить: если ваша функция потребляет несколько компонентов или компонент и другие значения одновременно, тогда ваша функция будет не HOC.
Причина этого правила проста: HOC должны быть составными. Используя что-то вроде функции compose
Recompose (HHOC), вы сможете написать:
const SuperEnhancedComponent = compose( myHOC, myHHOC(configObj), )(BoringComponent);
Функциональные HOC
Как и в случае с обычными компонентами React, если все, что вам нужно, - это добавить какие-то реквизиты или предоставить альтернативный рендеринг, не полагаясь на функции жизненного цикла, то этот шаблон для вас. Я бы даже сказал, что это должен быть ваш базовый шаблон по умолчанию. Переходите к другим только в том случае, если этот не подходит для вашего варианта использования. Вот пример того, что я написал, который отображает переданный компонент, только если существует DOM (другими словами, это предотвращает рендеринг компонента на стороне сервера, где DOM API, на которые компонент может полагаться, не существуют).
function onClientOnly(Component) { if (/* check for DOM */) { return Component; } return null; }
Вот немного более сложный, который вводит определенный стиль в переданный компонент:
function withDarkTheme(Component) { return ({ style, ...rest }) => ( <Component style={{ ...darkThemeObj, ...style }} {...rest} /> ); }
Шаблон довольно простой, но мощный. Опять же, это должно быть вашим основным способом написания HOC.
Классы HOC
Что делать, если вам нужно управлять жизненным циклом опоры, которую вам нужно внедрить? Как и в случае с обычными компонентами React, вам понадобится класс. Как и в случае с функциональными HOC, вы начнете с функции, которая принимает компонент, но на этот раз вы вернете новый компонент на основе класса, который отображает переданный компонент в методе render()
. Вот тот, который добавляет слушателя для нажатия клавиши ESC и передает опору onEsc
:
function withEscapeHandler(Component) { class componentWithEscapeHandler extends React.Component { constructor(props) { super(props); this.handleKeypress = this.handleKeypress.bind(this); } handleKeypress(event) { if (event.keyCode === 27) { this.props.onEsc(event); } } componentDidMount() { window.addEventListener('keypress', this.handleKeypress); } componentWillUnmount() { window.removeEventListener('keypress', this.handleKeypress); } render() { return ( <Component onEsc={this.handleKeypress} {...this.props} /> ); } } return componentWithEscapeHandler; }
HOC с инверсией наследования
Это последний и наименее используемый шаблон. Его следует использовать только тогда, когда вам нужен доступ к внутренним компонентам переданного компонента, таким как его состояние и методы. Для вас это должно звучать как красный флаг, потому что это так. Компоненты по большей части должны быть черными ящиками, придерживаясь принципа « программные объекты (классы, модули, функции и т. Д.) Должны быть открытыми для расширения, но закрытыми для модификации. » Использование инверсии наследования открывает и напрямую изменяет компонент.
Для этого напишите Class HOC, возвращаемый компонент которого расширяет переданный компонент вместо React.Component
. Это то, что дает HOC доступ к внутренним компонентам компонента. Ключевой момент, который следует помнить при написании HOC инверсии наследования, - это то, что к каждому методу из родительского элемента можно получить доступ из объекта super
. Я нашел только один вариант использования для этого:
function withFunctionAsAChild(Component) { class ComponentWithFACC extends Component { render() { const { children, ...props } = this.props; if (typeof children === 'function') { return children(props, this.state, this); } return super.render(); } } return ComponentWithFACC; }
Последние штрихи
Подобно тому, как компоненты React существуют для абстрагирования общей разметки, компоненты более высокого порядка существуют для абстрагирования общего поведения. При рассмотрении вопроса о создании какой-либо абстракции убедитесь, что шаблон уже существует, прежде чем вытаскивать наименьшее связное повторение в абстракцию. Используйте HOC для внедрения свойств, рендеринга альтернативной разметки и / или управления методами жизненного цикла. Также рекомендую соблюдать эти правила:
- Назовите каждую функцию и класс, чтобы упростить отладку. См. Документацию React по этому поводу.
- Передайте все неиспользуемые свойства базовому компоненту.
- Используйте
hoistNonReactStatics
в каждом HOC, чтобы сохранить статические свойства базового компонента. См. Документацию по React по этому поводу.