Асинхронные запросы

Нам все еще не нужен redux, ничего подобного.

CRA так устроен, что если вы положите что-нибудь в public директорию, это будет доступно по пути:

http://localhost:3000/название-директории/название-файла (но без слова public в пути)

Переместим наш json с новостями в src/public/data/newsData.json

Теперь его можно открыть GET-запросом на localhost:3000/data/newsData.json

Конечно, при этом у нас сломался импорт в App.js (так как такого файла по старому пути нет):

Так как у нас есть доступ к файлу через GET-запрос, мы можем запросить его.

"Давайте представим задачу" (c)

У нас есть данные на сервере (новости в json), нам нужно их запросить и отобразить в списке. Пока запрос выполняется, мы хотим показывать юзеру надпись: "Загружаю..." вместо списка новостей, чтобы он не нервничал. Когда новости загружены - мы хотим отобразить их как раньше.

Что нового в этой задаче:

  • как сделать асинхронный запрос (вопрос не про react) [1];

  • где делать асинхронный запрос (про react) [2];

[1] - это вопрос про нативный js. Запрос будем делать с помощью fetch.

[2] - запрос за данными следует начать в componentDidMount

Начнем с подготовки "состояния" и шаблона.

src/App.js

import React from 'react'
import { Add } from './components/Add'
import { News } from './components/News'
// удален импорт newsData
import './App.css'

class App extends React.Component {
  state = {
    news: null, // было newsData
    isLoading: false, // статус для манипуляций "прелоадером" ("Загружаю..." в нашем случае)
  }
  ...
}

export default App

В данный момент наше приложение не работает, но мы приготовили несколько важных вещей:

  • во-первых, мы сможем на основе данных в newsData сказать:

    • если newsData: null:

      • если isLoading: false - значит данные еще не были загружены или произошла ошибка;

      • если isLoading: true - данные еще загружаются

    • если newsData: [] (пустой массив) - значит новостей нет;

    • если `newsData: [данные про новости] - значит новости есть и они загружены;

В реакт-приложениях, все начинается с представления (описания) данных. Рисуйте в голове или на листочке, так как на основе такой шпаргалки, нам не составит труда сделать шаблон.

Начнем с составления выражений для шаблона. Первое:

{Array.isArray(news) && <News data={news} />}

То есть мы проверяем, если в this.state.news - массив - то рисуй компонент новости, он уже умеет рисовать "новостей нет" или список новостей.

Документация про isArray (MDN)

Второе условие:

{isLoading && <p>Загружаю...</p>}

Оформим все это в компоненте:

src/App.js

class App extends React.Component {
  state = {
    news: null,
    isLoading: false,
  }
  handleAddNews = data => {
    const nextNews = [data, ...this.state.news]
    this.setState({ news: nextNews })
  }
  render() {
    const { news, isLoading } = this.state // все необходимое взяли из state

    return (
      <React.Fragment>
        <Add onAddNews={this.handleAddNews} />
        <h3>Новости</h3>
        {isLoading && <p>Загружаю...</p>}
        {Array.isArray(news) && <News data={news} />}
      </React.Fragment>
    )
  }
}

Осталось сделать асинхронный вызов и установить правильное состояние.

Помните, мы с вами один раз описали, что количество новостей отображает цифры в зависимости от данных и когда стали добавлять новости - мы это место вообще не трогали, но счетчик работал корректно. Это декларативный подход. Так же и сейчас - мы в силу того, что я вижу всю картину, описали шаблон и как ему себя вести, а состояние разруливать будем на последнем шаге. Такой трюк может быть недоступен вам некоторое время, пока вы обучаетесь, поэтому пишите код как будет удобно, например делайте шаг за шагом что-то и воюйте с ошибками, главное - практика.

Вернемся к коду и сделаем fetch-запрос + console.log'и. Как я уже говорил, запрос за данными будем делать в момент, когда компонент уже примонтирован (то есть появился на странице, то есть нам нужен метод жизненного цикла - componentDidMount):

src/App.js

class App extends React.Component {
  ...
  componentDidMount() {
    fetch('http://localhost:3000/data/newsData.json')
      .then(response => {
        return response.json()
      })
      .then(data => {
        console.log(this)
        console.log('приехали данные ', data)
      })
  }
  ...
}

Посмотрите в network, все работает:

Заглянем в console, и увидим, что так как мы используем стрелочные функции - мы не потеряли this.

Рассказывать про то как работает promise я не буду, но если у вас есть вопросы, вот мои любимые материалы:

Урок подходит к логическому завершению. Осталось лишь корректно обновлять состояние компонента.

Задача: до запроса - сделать isLoading: true, после завершения запроса - обновить isLoading: false, и в news положить данные, пришедшие с сервера.

(так как решение в пару строк, я сделаю отступ. Очень хочу чтобы вы попробовали сами)

. . . . . .

Решение:

src/App.js

...
componentDidMount() {
  // ставим isLoading true, 
  // то есть запрос за даннмыи начался
  // фактически он начнется в строке с fetch,
  // но на переход от одной строки к другой
  // пройдут миллисекунды
  this.setState({ isLoading: true })
  fetch('http://localhost:3000/data/newsData.json')
    .then(response => {
      return response.json()
    })
    .then(data => {
      // запрос завершился успешно,
      // делаем isLoading: false
      // в news кладем пришедшие данные
      this.setState({ isLoading: false, news: data })
    })
}
...

Что интересно, у нас опять все работает. Мы не трогали компонент <News />, так как мы вновь просто изменили "источник данных".

Так как запрос за данными происходит на localhost, данные прилетают мнгновенно. Давайте искусственно затормозим этот момент, чтобы увидеть как они "загружаются". Добавим таймаут, конечно же.

src/App.js

...
componentDidMount() {
  this.setState({ isLoading: true })
  fetch('http://localhost:3000/data/newsData.json')
    .then(response => {
      return response.json()
    })
    .then(data => {
      setTimeout(() => { // добавили задержку
        this.setState({ isLoading: false, news: data })
      }, 3000) // в три секунды
    })
}
...

Подождите три секунды и увидите как появится список новостей. Причем, что хочется отметить - опять нам помогает React. Изменился state -> вызвался render. Никаких дополнительных манипуляций ;)

И никакого Redux/Mobx и прочего. Задача решена без "дичи" в виде кучи библиотек, которые здесь не уместны. Поздравляю.

Итого: научились выполнять асинхронные запросы и показывать прелоадер.

Исходный код.

Last updated