Хочу поделиться ещё одним велосипедом — в первую очередь, чтобы получить бесценные советы. Очень может быть, что есть способы получше. Дополнительные примеры применения описанного способа можно посмотреть в исходниках фан-проекта на GitHub.
Почти все страницы в проекте обернуты компонентом Page:
const MyPage = () => ( <Page> Hello World </Page> )
Для загрузки внешних данных, у компонента Page есть три передаваемых свойства (props):
- Функция обратного вызова onMounted вызывается внутри метода жизненного цикла компонента componentDidMount; согласно документации React-а, именно в этом месте рекомендуется загружать внешние данные.
- Флаг isLoading мы передаём перед загрузкой внешних данных — true, и после завершения этой операции — false.
- Флаг isNotFound мы передаём, если загрузка внешних данных не увенчалась успехом.
Пример с применением Redux:
// components/Post/PostViewPage.js const PostViewPage = ({ post, ...props }) => ( <Page {...props}> <Post {...post} /> </Page> ) const mapStateToProps = (state) => ({ post: state.postView, isNotFound: isEmpty(state.postView), }) const mapDispatchToProps = (dispatch, ownProps) => ({ onMounted: () => { const id = parseInt(ownProps.match.params.id, 10) dispatch(actions.read(id)) } }) export default connect(mapStateToProps, mapDispatchToProps)(PostViewPage)
Обратите внимание, флаг isLoading не передаётся в props явно, он привязывается через mapStateToProps в компоненте Page (что будет продемонстрировано ниже по тексту).
Если у вас возникают вопросы по выражениям:
// деструктуризация и рест-параметры { post, ...props } // спред {...props}
… то можно обратиться к справочнику MDN:
Сайд-эффект actions.read(id) обеспечивает redux-thunk:
// ducks/postView.js const read = id => (dispatch) => { // установить флаг state.app.isLoading dispatch(appActions.setLoading(true)) // сбросить значение state.postView dispatch(reset()) // флаг о завершении таймаута let isTimeout = false // флаг о завершении загрузки let isFetch = false setTimeout(() => { isTimeout = true if (isFetch) { dispatch(appActions.setLoading(false)) } }, 500) // демонстрировать state.app.isLoading не менее 500 мс fetch(`/post/${id}`) .then(response => { const post = response.data // записать данные в state.posts dispatch(postsActions.setPost(post)) // записать данные в state.postView dispatch(set(post)) isFetch = true if (isTimeout) { dispatch(appActions.setLoading(false)) } }) .catch(error => { isFetch = true if (isTimeout) { dispatch(appActions.setLoading(false)) } dispatch(appActions.setMainError(error.toString())) }) }
Когда данные загружаются слишком быстро, то возникает неприятый визуальный эффект мигания индикатора загрузки. Чтобы этого избежать, добавил таймер на 500 мс и логику на флагах isTimeout и isFetch.
Компонент Page, если отбросить прочие украшательства, обеспечивает процесс загрузки внешних данных:
// components/Page/Page.js class Page extends React.Component { _isMounted = false componentDidMount() { this._isMounted = true const { onMounted } = this.props if (onMounted !== void 0) { onMounted() } } render() { const { isNotFound, isLoading, children } = this.props if (this._isMounted && !isLoading && isNotFound) { return <NotFound /> } return ( <div> <PageHeader /> <div> {this._isMounted && !isLoading ? children : <div>Загрузка...</div> } </div> <PageFooter /> </div> ) } } const mapStateToProps = (state, props) => ({ isLoading: state.app.isLoading }) export default connect(mapStateToProps)(Page)
Как это работает? Первый проход render выполнится с выключенным флагом _isMounted — отображение индикатора загрузки. Далее выполнится метод componentDidMount, где включится флаг _isMounted и выполнится функция обратного вызова onMounted; внутри onMounted мы вызываем сайд-эффект (например, actions.read(id)), где включится флаг state.app.isLoading, что вызовет новый render — по прежнему отображение индикатора загрузки. После асинхронного вызова fetch внутри нашего сайд-эффекта, выключится флаг state.app.isLoading, что вызовет новый render — но теперь, вместо отображения индикатора загрузки, выполнится render вложенного компонента (children); но если включить флаг isNotFound, то вместо render-а для вложенного компонента (children), выполнится render компонента <NotFound />.
ссылка на оригинал статьи https://habrahabr.ru/post/327422/
Добавить комментарий