React: компоненты-контейнеры
July 06, 2020
Всем привет! В Реакте есть один паттерн, который оказывает большое влияние на то, как мы разрабатываем приложения — компоненты-контейнеры. Идея довольно простая:
Контейнер получает данные с бэкэнда и затем рендерит соответствующий дочерний компонент. И всё.
Под соответствующим компонентом понимается компонент с таким же именем, к примеру:
StockWidgetContainer => StockWidget
TagCloudContainer => TagCloud
PartyPooperListContainer => PartyPooperList
Уловили идею?
Почему контейнеры?
Предположим, что нам нужно отобразить список комментариев к статье. И если тебе не было известно о концепции компонентов-контейнеров, то всё будет свалено в кучу и код будет выглядеть как-то так:
class CommentList extends React.Component {
state = {
comments: [],
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/comments')
.then(response => response.json())
.then(comments => this.setState({ comments: comments }))
}
render() {
return (
<ul>
{this.state.comments.map(c => (
<li key={c.id}>
{c.body}--{c.name}
</li>
))}
</ul>
)
}
}
Один и тот же компонент ответственен, как за получение данных, так и за их отображение. В принципе, ничего особенно ужасного в этом коде нет, но при такой код точно нельзя назвать идиоматичным и приводит к неиспользованию некоторых фич Реакта:
- Переиспользование
Компонент не может быть переиспользован, всё жёстко прибито гвоздями. Разве что в случае если где-то потребуется отобразить один в один такие же комментарии, но вряд ли.
- Структура данных
Компоненты должны явно показывать, какие данные они ожидают. Для этого идеально подходят пропсы совместно с PropTypes. Компонент тихо перестанет отображать комментарии, если апи на бэкэ хоть немного изменится (скорее всего, бэки специально ломать апи не будут, но всякое возможно, особенно при использовании каких-то third-party api).
Давайте ещё раз. В этот раз с контейнерами.
Первым делом, отделим логику получения данных в отдельный компонент-контейнер.
class CommentListContainer extends React.Component {
state = {
comments: [],
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/comments')
.then(response => response.json())
.then(comments => this.setState({ comments: comments }))
}
render() {
return <CommentList comments={this.state.comments} />
}
}
Теперь переработаем компонент CommentList, передавая комментарии, как пропсы:
class CommentList extends React.Component {
render() {
return (
<ul>
{this.props.comments.map(c => (
<li key={c.id}>
{c.body}--{c.name}
</li>
))}
</ul>
)
}
}
Так что мы получили?
Многое =)
- Мы разделили бизнес-логику и логику отображения.
- Мы сделали наш CommentList переиспользуемым.
- Мы дали возможность компоненту проверять типы входных параметров и он будет громко ругаться, если в него начнут подсовывать что-то неправильное.
Удачного кодинга!