Я переписываю свое приложение для использования Flux, и у меня проблема с получением данных из магазинов. У меня много компонентов, и они очень много вложены. Некоторые из них большие (Article
), некоторые маленькие и простые (UserAvatar
, UserLink
).
Я боролся с тем, где в иерархии компонентов мне следует читать данные из магазинов.
Я попробовал два крайних подхода, ни один из которых мне не очень понравился:
Все компоненты сущности читают свои собственные данные
Каждый компонент, которому нужны данные из Store, получает только идентификатор объекта и извлекает объект самостоятельно.
Например, Article
передается articleId
, UserAvatar
и UserLink
передаются userId
.
У этого подхода есть несколько существенных недостатков (обсуждаемых в примере кода).
var Article = React.createClass({
mixins: [createStoreMixin(ArticleStore)],
propTypes: {
articleId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
article: ArticleStore.get(this.props.articleId);
}
},
render() {
var article = this.state.article,
userId = article.userId;
return (
<div>
<UserLink userId={userId}>
<UserAvatar userId={userId} />
</UserLink>
<h1>{article.title}</h1>
<p>{article.text}</p>
<p>Read more by <UserLink userId={userId} />.</p>
</div>
)
}
});
var UserAvatar = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<img src={user.thumbnailUrl} />
)
}
});
var UserLink = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<Link to='user' params={{ userId: this.props.userId }}>
{this.props.children || user.name}
</Link>
)
}
});
Минусы этого подхода:
- Очень неприятно иметь сотни компонентов, потенциально подписывающихся на магазины;
- Трудно отслеживать, как и в каком порядке обновляются данные, потому что каждый компонент извлекает свои данные независимо;
- Даже если у вас может уже быть сущность в состоянии, вы вынуждены передать ее идентификатор дочерним элементам, которые получат ее снова (или нарушат согласованность).
Все данные считываются один раз на верхнем уровне и передаются компонентам.
Когда мне надоело выявлять ошибки, я попытался перенести все извлечение данных на верхний уровень. Однако это оказалось невозможным, потому что для некоторых сущностей у меня есть несколько уровней вложенности.
Например:
Category
содержитUserAvatar
людей, которые вносят свой вклад в эту категорию;Article
может иметь несколькоCategory
.
Поэтому, если бы я хотел получить все данные из Магазинов на уровне Article
, мне нужно было бы:
- Получить статью из
ArticleStore
; - Получить все категории статей из
CategoryStore
; - Отдельно получить участников каждой категории из
UserStore
; - Каким-то образом передать все эти данные компонентам.
Еще более неприятно то, что всякий раз, когда мне нужна глубоко вложенная сущность, мне нужно было бы добавить код на каждый уровень вложенности, чтобы дополнительно передать ее вниз.
Подведение итогов
Оба подхода кажутся ошибочными. Как мне решить эту проблему наиболее элегантно?
Мои цели:
У магазинов не должно быть безумного количества подписчиков. Глупо для каждого
UserLink
слушатьUserStore
, если родительские компоненты уже это делают.Если родительский компонент получил какой-либо объект из магазина (например,
user
), я не хочу, чтобы какие-либо вложенные компоненты снова извлекали его. Я смогу передать это через реквизит.Мне не нужно извлекать все сущности (включая отношения) на верхнем уровне, потому что это усложнит добавление или удаление отношений. Я не хочу вводить новые реквизиты на всех уровнях вложенности каждый раз, когда вложенная сущность получает новое отношение (например, категория получает
curator
).