Компоненты высшего порядка
Компонент высшего порядка (HOC) - это продвинутая техника в React для повторного использования логики компонента. HOC на самом деле не являются частью React API. Они - образец, который вытекает из композиционной природы React.
Конкретно, компонент более высокого порядка - это функция, которая принимает компонент и возвращает новый компонент.
const EnhancedComponent = upperOrderComponent (WrappedComponent);
В то время как компонент преобразует реквизиты в пользовательский интерфейс, компонент более высокого порядка преобразует компонент в другой компонент.
HOC распространены в сторонних библиотеках React, таких как Redux's connect и Relay's createFragmentContainer.
В этом уроке мы обсудим, почему компоненты высшего порядка полезны, и как написать свой собственный.
HOCs для сквозных проблем
Компоненты являются основной единицей повторного использования кода в React. Тем не менее, вы обнаружите, что некоторые шаблоны не подходят для традиционных компонентов.
Например, допустим, у вас есть компонент CommentList, который подписывается на внешний источник данных для отображения списка комментариев:
class CommentList extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
// "DataSource" is some global data source
comments: DataSource.getComments()
};
}
componentDidMount() {
// Subscribe to changes
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
// Clean up listener
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
// Update component state whenever the data source changes
this.setState({
comments: DataSource.getComments()
});
}
render() {
return (
<div>
{this.state.comments.map((comment) => (
<Comment comment={comment} key={comment.id} />
))}
</div>
);
}
}
Позже вы пишете компонент для подписки на один пост в блоге, который следует похожему шаблону:
class BlogPost extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
blogPost: DataSource.getBlogPost(props.id)
};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
blogPost: DataSource.getBlogPost(this.props.id)
});
}
render() {
return <TextBlock text={this.state.blogPost} />;
}
}
CommentList и BlogPost не идентичны - они вызывают разные методы в DataSource и отображают разные выходные данные. Но большая часть их реализации одинакова:
- При монтировании добавьте прослушиватель изменений в DataSource.
- Внутри слушателя вызывайте setState при каждом изменении источника данных.
- При размонтировании удалите прослушиватель изменений.
Вы можете представить, что в большом приложении один и тот же шаблон подписки на DataSource и вызова setState будет происходить многократно. Нам нужна абстракция, которая позволяет нам определять эту логику в одном месте и делить ее между многими компонентами. Именно здесь компоненты высшего порядка выделяются.
Мы можем написать функцию, которая создает компоненты, такие как CommentList и BlogPost, которые подписываются на DataSource. Функция будет принимать в качестве одного из своих аргументов дочерний компонент, который получает подписанные данные в качестве реквизита.
Давайте назовем функцию с подпиской:
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
const BlogPostWithSubscription = withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id)
);
Первый параметр - это обернутый компонент. Второй параметр извлекает данные, которые нас интересуют, учитывая DataSource и текущий реквизит.
Когда визуализируются CommentListWithSubscription и BlogPostWithSubscription, CommentList и BlogPost будут переданы данные с самыми последними данными, полученными из DataSource:
// This function takes a component...
function withSubscription(WrappedComponent, selectData) {
// ...and returns another component...
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
// ... that takes care of the subscription...
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// ... and renders the wrapped component with the fresh data!
// Notice that we pass through any additional props
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
Обратите внимание, что HOC не изменяет компонент ввода и не использует наследование для копирования своего поведения. Скорее, HOC создает исходный компонент, заключая его в компонент контейнера. HOC - это чистая функция с нулевыми побочными эффектами.
И это все! Обернутый компонент получает все реквизиты контейнера вместе с новой реквизитом данных, которые он использует для визуализации своего вывода. HOC не заботится о том, как или почему используются данные, а обернутый компонент не имеет отношения к тому, откуда поступили данные.
Поскольку с Subscription это обычная функция, вы можете добавить столько аргументов, сколько захотите. Например, вы можете захотеть сделать имя настраиваемого свойства данных для дальнейшей изоляции HOC от обернутого компонента. Или вы можете принять аргумент, который конфигурирует shouldComponentUpdate, или аргумент, который конфигурирует источник данных. Все это возможно, потому что HOC имеет полный контроль над тем, как определяется компонент.
Как и компоненты, контракт между withSubscription и упакованным компонентом полностью основан на реквизите. Это облегчает замену одного HOC на другой, если они обеспечивают одинаковые реквизиты для обернутого компонента.
Новый контент: Composer: менеджер зависимостей для PHP , R программирования