Загрузка внешних данных из REST API

от автора

Хочу поделиться ещё одним велосипедом — в первую очередь, чтобы получить бесценные советы. Очень может быть, что есть способы получше. Дополнительные примеры применения описанного способа можно посмотреть в исходниках фан-проекта на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *