Купить официальную электронную книгу

Если вы хотите поддержать автора и получить копию книги в формате PDF, EPUB и / или MOBI, пожалуйста, купите официальную электронную книгу.

Предпосылки

Глава 1 | Основные концепции
Глава 2 | Практика основ
Глава 3 | Реализация с помощью React

Сфера действия этой главы

В предыдущей главе мы смогли создать наше первое приложение React & Redux. Однако все наши данные были определены вручную. В реальном сценарии мы будем работать с API для получения данных для заполнения нашего пользовательского интерфейса. Работа с API также заставляет задуматься о дополнительных вещах. В этой главе мы попрактикуемся в работе с Twitch API, распаковывая, как создать асинхронное приложение Redux.

Настройка проекта

Мы собираемся использовать Twitch API для получения 25 избранных потоков. Мы визуализируем изображения для предварительного просмотра каждого потока, которые переводят пользователя в поток одним щелчком мыши.

Первоначальная настройка проекта будет таким же, как и в предыдущей главе. Чтобы сэкономить время, скачайте шаблон этого проекта через GitHub.

Вы можете удалить несколько файлов dummy.txt.

Действия для асинхронного приложения

Когда мы работаем с API, наше приложение становится асинхронным. Это означает, что нашему пользовательскому интерфейсу придется ждать, пока будут получены данные, прежде чем они будут отображены в соответствии с желанием. В нашем случае нам нужно дождаться ответа на наш запрос на получение потокового видео Twitch, прежде чем мы сможем заполнить наш пользовательский интерфейс.

Действия

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

Первое действие позволит редуктору, с которого был запущен запрос API.

Когда редуктор получает это действие, мы можем отобразить загрузчик в пользовательском интерфейсе. Вот созданный мной пример загрузчика:

Второе действие сообщит редуктору, что запрос API получил успешный ответ.

На этом этапе мы отключим загрузчик и отрендерим 25 потоков Twitch в пользовательский интерфейс вместо него.

Третье действие сообщит редуктору, что запрос API не выполнен.

Если это произойдет, мы отобразим сообщение об ошибке в пользовательском интерфейсе.

В нашем состоянии будет свойство под названием status, которое мы можем инициализировать и обновить с помощью наших действий следующим образом:

status: "" //initial
status: "loading" //API request began
status: "success" //API request successful
status: "error" //API request failed

Имея эту информацию, доступную нашим компонентам контейнера React, мы будем использовать условную визуализацию, чтобы определить, что отображается в пользовательском интерфейсе.

Вот несколько примеров объектов действия:

{ type: 'FETCH_REQUEST', status: "loading" }
{ type: 'FETCH_FAILURE', status: "error", error: 'Request failed.' }
{ type: 'FETCH_SUCCESS', status: "success", response: { ... } }

Зная все это, приступим к написанию асинхронных действий с создателями действий для нашего проекта.

Создатели действий

Начнем с нового файла в нашей папке actions под названием FetchRequest.js.

Мы можем добавить следующее:

//define action within an action creator
function FetchRequest() {
  const FETCH_REQUEST = 'FETCH_REQUEST'
  return {
    type: FETCH_REQUEST,
    status: "loading"
  }
}
export default FetchRequest

Этот код очень прост. Нам просто нужно сообщить редуктору, что запрос на выборку начался, чтобы наш пользовательский интерфейс мог отобразить загрузчик для фазы «загрузки».

Затем мы можем создать еще один файл в нашей папке actions с именем FetchSuccess.js и добавить следующий код:

//define action within an action creator
function FetchSuccess(streams) {
  const FETCH_Success = 'FETCH_Success'
  return {
    type: FETCH_SUCCESS,
    status: "success",
    streams
  }
}
export default FetchSuccess

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

Последним создателем асинхронного действия будет FetchFailure.js:

//define action within an action creator
function FetchFailure(error) {
  const FETCH_FAILURE = 'FETCH_FAILURE'
  return {
    type: FETCH_FAILURE,
    status: "error",
    error
  }
}
export default FetchFailure

В этом коде мы определяем состояние «ошибка» и сообщение об ошибке, которое передается в качестве параметра.

Вот так наши действия и создатели действий готовы! Не плохо!

Определение нашего редуктора

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

В папке reducer создайте новый файл под названием TwitchApp.js.

В этом файле давайте продолжим и создадим объект начального состояния:

//define the initial state
const initialState = {
  status: "",
  streams: [],
  error: ""
}

Здесь мы имеем свойство хранить статус, который будет обновляться действиями, которые мы только что определили. У нас также есть свойство под названием streams, которое будет содержать массив потоков Twitch, полученных с помощью нашего запроса API. Наконец, у нас есть свойство error для хранения сообщения об ошибке в случае сбоя.

Затем давайте добавим оболочку нашего редуктора и обработку действия FETCH_REQUEST:

//define a reducer with an initalized state and logic to handle action
function TwitchApp(state = initialState, action) {
  switch(action.type) {
    case 'FETCH_REQUEST':
      const requested = Object.assign({}, state, {
        status: action.status
      })
      return requested
    default:
      return state
  }
}
export default TwitchApp

В действии FETCH_REQUEST мы копируем текущее состояние с обновленным статусом, который будет «загружаться».

В действии FETCH_SUCCESS мы копируем текущее состояние с обновленным статусом (которое будет «успешным») и потоки:

switch(action.type) {
  case 'FETCH_REQUEST':
    const requested = Object.assign({}, state, {
      status: action.status
    })
    return requested
  case 'FETCH_SUCCESS':
    const successful = Object.assign({}, state, {
      status: action.status,
      streams: action.streams
    })
    return successful
  default:
    return state
}

Наконец, мы добавляем логику для нашего действия FETCH_FAILURE, которое включает обновление статуса и ошибки:

switch(action.type) {
  case 'FETCH_REQUEST':
    const requested = Object.assign({}, state, {
      status: action.status
    })
    return requested
  case 'FETCH_SUCCESS':
    const successful = Object.assign({}, state, {
      status: action.status,
      streams: action.streams
    })
    return successful
  case 'FETCH_FAILURE':
    const failed = Object.assign({}, state, {
      status: action.status,
      error: action.error
    })
    return failed
  default:
    return state
}

На данный момент наш редуктор готов!

Создание нашего загрузчика

Настройка нашего контейнерного компонента

Пока что мы выполнили настройку нашего исходного кода Redux. Теперь пора приступить к завершению компонентов React. В этом разделе мы хотим, чтобы наш компонент контейнера React выполнял запрос API и отправлял действие FETCH_REQUEST.

Во-первых, нам нужно инициализировать хранилище. Откройте index.js.

Начнем с импорта createStore из библиотеки Redux:

import { createStore } from 'redux';

Также нам нужно импортировать наш редуктор:

import TwitchApp from './reducers/TwitchApp';

Затем мы можем добавить инициализацию хранилища между классом и ReactDOM.render(...):

//initialize store
let store = createStore(TwitchApp)

Затем нам нужно сделать хранилище доступным для компонента контейнера, чтобы можно было получить состояние.

Импортируйте предопределенный компонент Provider из библиотеки React-Redux:

import { Provider } from 'react-redux';

Затем оберните компонент Provider вокруг вложенного компонента <App/> в рендере ReactDOM, передав состояние в качестве опоры:

ReactDOM.render(
  <Provider store = { store }>
    <App/>
  </Provider>,
  document.getElementById('app')
)

Наконец, мы можем импортировать компонент контейнера, который мы создадим в следующий раз, под названием Streams и вложить его в передаваемую опору магазина:

import Streams from './components/containers/Streams';
//top level of React component hierarchy
class App extends React.Component {
  render() {
    return (
      <div className="app">
        <Streams store={store} />
      </div>
    )
  }
}

Прохладный! Перейдем к нашему компоненту-контейнеру, который будет создан в файле с именем Streams.js. Обязательно поместите этот файл в папку контейнеры.

Добавим немного кода:

import React from 'react';
import { getState } from 'redux';
import FetchRequest from '../../actions/FetchRequest';
import FetchSuccess from '../../actions/FetchSuccess';
import FetchFailure from '../../actions/FetchFailure';
//Provider/Container React Component
class Streams extends React.Component {
  render() {
    const stateProps = this.props.store.getState();
    return (
      <div>
      </div>
    )
  }
}
export default Streams

В приведенном выше коде мы импортируем getState, чтобы сохранить состояние в переменной, что мы и сделали в следующей строке:

const stateProps = this.props.store.getState();

Мы также уже импортировали создателей действий.

Вскоре мы можем написать код для выполнения запроса API и отправки действия в это время. Перед этим давайте запустим код условного рендеринга, чтобы мы могли рендерить наш загрузчик, если статус в состоянии установлен на «загрузка».

Во-первых, мы можем сохранить статус в переменной чуть выше return:

const status = stateProps.status;

Добавление условного рендеринга

Создайте новый файл в папке Presentations с именем Loader.js.

В этом файле мы просто сосредоточимся на представлении загрузчика. Для этого загрузчика я собираюсь использовать этот чистый загрузчик кристаллов CSS, который я сделал на Codepen:

Я просто предоставлю код, но если вам интересно, как это сделать, у меня есть видеоурок на YouTube, который вы можете посмотреть.

import React from 'react';
//Presentational React Component
class Loader extends React.Component {
  render() {
    return (
      <div className="loader">
        <div className="box">
          <div className="line-1" />
          <div className="line-2">
            <div className="triangle">
              <div className="shine" />
            </div>
          </div>
          <div className="line-3">
            <div className="triangle-sm">
              <div className="shine" />
            </div>
          </div>
          <div className="line-4">
            <div className="triangle-sm">
              <div className="shine" />
            </div>
          </div>
        </div>
      </div>
    )
  }
}
export default Loader

Кроме того, вот обновленный файл main.css через GitHub Gist.

Затем откройте Streams.js и давайте импортируем этот новый презентационный компонент:

import Loader from '../presentationals/Loader';

Затем мы можем добавить условный рендеринг, который будет выглядеть так: «Если status равен загрузке, то рендеринг загрузчика. В противном случае визуализируйте пустой div ».

return (
  <div>
  {status === "loading" ? (
     <Loader />
    ) : (
      <div></div>
    )
  }
  </div>
)

В настоящее время мы не должны ничего видеть на локальном хосте, потому что по умолчанию статус не загружается. На данный момент мы можем убедиться, что наш условный рендеринг работает, изменив значение переменной status на «loading»:

const status = "loading";

Теперь мы должны увидеть, что следующий загрузчик работает правильно:

Аккуратный!

Давайте закончим этот раздел, сделав запрос API и изменив состояние с помощью отправленного действия, чтобы наш загрузчик отображал, когда для свойства status установлено значение «загрузка».

Создание ключа API

Во-первых, создайте учетную запись Twitch, если вы еще этого не сделали.

Затем мы можем создать ключ API Twitch, посетив страницу подключений.

В разделе Другие подключения вы можете нажать, чтобы зарегистрировать приложение и получить ключ API.

При регистрации приложения обязательно укажите URI перенаправления как http: // localhost:

Нажмите Зарегистрировать, скопируйте созданный идентификатор клиента и вставьте его в пустой файл в редакторе кода.

Мы сможем использовать этот идентификатор клиента для получения нужных нам данных.

Установка Axios

Для выполнения запросов к API мы можем использовать Axios.

Мы можем установить его, выполнив в командной строке следующее:

npm install axios --save

Прежде чем двигаться дальше, давайте импортируем это в Streams.js, где мы будем выполнять вызов API:

import axios from 'axios';

Запрос API и отправка нашего действия

Мы хотим получать потоковое видео с Twitch, которое можно воспроизводить. Мы хотим отобразить изображение обложки видео (превью) 25 потоков, которые ссылаются на прямой эфир.

Мы будем получать данные потоков из нашего компонента контейнера Streams.

Мы хотим получить эти потоковые видео до монтирования нашего компонента приложения, поэтому мы добавляем следующее в Streams.js:

componentWillMount() {
    axios.get('')
      .then(response => {
      })
      .catch(e => {

      });
  }

Приведенный выше код является оболочкой запроса API Axios. Его можно читать так: «Получите нужные нам данные по этому URL-адресу, а затем давайте что-нибудь сделаем с ответом. Если вы столкнетесь с ошибкой, дайте нам код ошибки, чтобы мы могли что-то с ней сделать ».

В axios.get('') мы можем поместить URL-адрес, который будет возвращать избранные потоки Twitch. URL можно найти в официальной документации Twitch API:

По умолчанию он дает нам 25 избранных потоков, которые нам и нужны.

Зная это, давайте вставим следующий URL:

componentWillMount() {
    axios.get('https://api.twitch.tv/kraken/streams/featured')
      .then(response => {
      })
      .catch(e => {

      });
  }

Теперь нам нужен параметр URL, который аутентифицирует нас для получения данных. Мы добавляем client_id, который мы сгенерировали ранее (он должен был быть помещен в пустой файл в вашем редакторе кода):

//update with your client_ID
componentWillMount() {
    axios.get('https://api.twitch.tv/kraken/streams/featured?client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
      .then(response => {
      })
      .catch(e => {

      });
  }

Теперь наш код говорит: «Привет, Twitch! У нас есть адрес для получения избранных потоков и подтверждения того, что вы согласны со мной. Как только вы дадите нам то, что нам нужно, тогда нам придется что-то делать с тем, что вы нам дали. Если вы поймали ошибку, сообщите нам, чтобы мы могли с ней что-то сделать. ”

Давайте посмотрим, работает ли это, зарегистрировав ответ (мы также можем зарегистрировать ошибку, если она не работает):

componentWillMount() {
  axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
    .then(response => {
      console.log(response);
    })
    .catch(e => {
      console.log(error);
    });
}

Обновите локальный хост и проверьте консоль. Мы должны увидеть, что возвращаются следующие данные:

В извлеченных данных (Object) есть данные (Object) для 25 избранных потоков в массиве под названием Featured.

В одном из представленных объектов потока есть другой объект с именем stream, содержащий необходимую нам информацию (URL-адрес обложки / изображения для предварительного просмотра и идентификатор):

Это краткий обзор того, как мы будем визуализировать потоки, когда получим успешный ответ. Однако давайте просто побеспокоимся об отправке нашего FETCH_REQUEST действия через FetchRequest() средство создания действия.

Еще раз, это установит статус, равный «загрузка», что должно привести к рендерингу нашего загрузчика.

Во-первых, давайте поместим запрос API в отдельную функцию, которая вызывается из ловушки жизненного цикла componentWillMount:

componentWillMount () {
  this.apiRequest();
}
apiRequest () {
  axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
    .then(response => {
      console.log(response);
    })
    .catch(e => {
      console.log(error);
    });
}

Затем давайте создадим функцию, которая будет отправлять действие FETCH_REQUEST и вызывать его из ловушки жизненного цикла componentWillMount:

componentWillMount () {
  this.apiRequest();
  this.dispatchFetchRequest();
}
//apiRequest() here
dispatchFetchRequest () {
  this.props.store.dispatch(FetechRequest());
}

Наконец, мы добавляем подписку и принудительное обновление нашего компонента в качестве обратного вызова:

componentWillMount () {
  this.props.store.subscribe(this.forceUpdate.bind(this));
  this.apiRequest();
  this.dispatchFetchRequest();
}

Собственно… еще кое-что. Обновление свойства status в нашем состоянии с помощью этого отправленного действия произойдет так быстро, что будет трудно заметить, работает ли оно вообще. Поэтому давайте приостановим отправленное действие на 5 секунд:

componentWillMount () {
  this.props.store.subscribe(this.forceUpdate.bind(this));
  this.apiRequest();
  setTimeout( () => {
    this.dispatchFetchRequest();
  }, 5000)
}

Давайте проверим локальный хост, чтобы убедиться, что он работает:

Woohoo! Одно действие проиграно, осталось еще два!

Обязательно удалите добавленную задержку, чтобы у нас было только это:

componentWillMount () {
  this.props.store.subscribe(this.forceUpdate.bind(this));
  this.apiRequest();
  this.dispatchFetchRequest();
}

Потоки рендеринга

Отправка наших действий

Следующим шагом является отправка нашего FETCH_SUCCESS действия через FetchSuccess() средство создания действия.

Нам нужно, чтобы это произошло, когда мы получим ответ. Таким образом, мы можем:

apiRequest () {
  axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
    .then(response => {
      this.dispatchFetchSuccess()
      console.log(response);
    })
    .catch(e => {
      console.log(error);
    });
}
//dispatchFetchRequest here
dispatchFetchSuccess () {
  this.props.store.dispatch(FetchSuccess());
}

Если мы посмотрим на FetchSuccess.js, нам также нужно передать потоки этому создателю действия:

//define action within an action creator
function FetchSuccess(streams) {
  const FETCH_SUCCESS = 'FETCH_SUCCESS'
  return {
    type: FETCH_SUCCESS,
    status: "success",
    streams
  }
}
export default FetchSuccess

Следовательно, нам нужно получить потоки в массив с именем streams из ответа (путь показан ниже), передать массив потоков в dispatchFetchSuccess(), например, this.dispatchFetchSuccess(streams) .

Сначала давайте получим потоки из ответа в массив с именем streams и передадим его dispatchFetchSuccess:

apiRequest () {
  axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
    .then(response => {
      const streams = response.data.featured.map(function(feat) {
        return feat.stream;
      });
      this.dispatchFetchSuccess(streams);
    })
    .catch(e => {
      console.log(error);
    });
}

Код для хранения потоков, показанный выше, можно прочитать как: «Для каждого избранного потока в избранном массиве вернуть объект потока этого избранного потока». Это может показаться сложным, но это довольно просто, если вы знаете, как следовать по пути ответа:

После этого мы можем добавить параметр streams и передать массив streams в создатель действия:

dispatchFetchSuccess (streams) {
  this.props.store.dispatch(FetchSuccess(streams));
}

Отрисовка в соответствии с состоянием обновления

На этом этапе наше состояние должно теперь содержать статус «успех» и массив, полный объектов потока в FETCH_SUCCESSaction.

Следующим шагом является обновление нашего условного рендеринга для этого обновления состояния.

Как и в предыдущей главе, мы хотим визуализировать несколько презентационных компонентов аналогичным образом:

const bookItems = stateProps.books.map((book) =>
  <BookCard
    key = { book }
    stateProps = { stateProps }
    dispatchAction = {this.dispatchAction.bind(this)}
  />
);
return (
  <div className="books-container">
    {bookItems}
  </div>
)

Давайте начнем этот шаг с создания нового файла с именем StreamCard.js в папке Presentationals.

Затем давайте импортируем это в Streams.js следующим образом:

import StreamCard from  '../presentationals/StreamCard';

Над возвратом мы можем сохранить компонент StreamCard для каждого потока в переменной с именем streamCardItems. Мы передаем каждому компоненту ключ, изображение для предварительного просмотра и URL-адрес потока (канала):

const streamCardItems = stateProps.streams.map((stream) =>
  <StreamCard
    key = { stream._id }
    streamCover = { stream.preview.medium }
    streamLink = { stream.channel.url }
  />
);

Вот где мы получили stream.preview.medium и stream.channel.url:

Затем мы обновляем наш условный рендеринг, чтобы он читался следующим образом: «Если статус равен загрузке, рендеринг загрузчика. В противном случае, если статус равен загрузке, визуализируйте карты потоков. В противном случае визуализируйте пустой div ».

return (
  <div>
  {status === "loading" ? (
     <Loader />
   ) : (
      status === "success" ? (
        <div className="stream-cards">
        {streamCardItems}
        </div>
      ) : (
        <div></div>
      )
    )
  }
  </div>
)

Обратите внимание, что className=”stream-cards” используется для стилизации.

Последний шаг - заполнить наш файл StreamCard.js следующим образом:

import React from 'react';
//Presentational React Component
class StreamCard extends React.Component {
  render() {
    return (
      <div className="stream-cards">
        <a href={this.props.streamLink}>
          <img 
            className="stream-cover"
            src={this.props.streamCover}
          />
        </a>
      </div>
    )
  }
}
export default StreamCard

В приведенном выше коде мы используем this.props.streamLink для привязки ссылки каждой отображаемой карты потока. Мы также используем this.props.streamCover для привязки источника каждого изображения в нашей карточке потока.

Есть также несколько классов для стилизации. Кстати, обновите main.css до следующего кода, найденного в этом GitHub Gist.

Идите и проверьте локальный хост. Теперь мы должны увидеть следующее:

Очень круто! Мы можем просмотреть изображения для всех 25 потоков и щелкнуть, чтобы просмотреть их.

Если мы продолжим обновлять страницу, мы увидим, что наш загрузчик работает:

Если у пользователя медленное соединение, загрузчик будет отображаться до тех пор, пока на запрос не будет получен ответ, что значительно улучшает взаимодействие с пользователем.

Отображение нашей ошибки

Последняя часть этого приложения - отправить наше FETCH_FAILURE действие и отобразить предупреждение с сообщением об ошибке.

Мы можем добавить новую функцию, которая будет отправлять FetchFailure() с ошибкой:

dispatchFetchFailure (error) {
  this.props.store.dispatch(FetchFailure(error));
}

Эта функция будет вызвана, если запрос API axios получит ошибку:

apiRequest () {
  axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
    .then(response => {
      console.log(response);
      const streams = response.data.featured.map(function(feat) {
        return feat.stream;
      });
      this.dispatchFetchSuccess(streams);
    })
    .catch(e => {
      this.dispatchFetchFailure(e);
    });
}

Обратите внимание, как мы передали ошибку в this.dispatchFetchFailure. Отправленное действие для сбоя обновит свойство error в нашем состоянии с полученным сообщением об ошибке.

Затем мы можем вставить презентационный компонент для нашего оповещения, когда статус не «загрузка» или «успех», а «ошибка»:

return (
  <div>
  {status === "loading" ? (
     <Loader />
   ) : (
      status === "success" ? (
        <div className="stream-cards">
        {streamCardItems}
        </div>
      ) : (
        status === "error" ? (
          <div>
            //insert alert presentational component here
          </div>
        ) : (
          <div></div>
        )
      )
    )
  }
  </div>
)

Над возвратом мы можем сохранить текущее сообщение об ошибке в переменной:

const error = stateProps.error;

Создайте новый файл с именем Alert.js в папке Presentations и добавьте следующий код:

import React from 'react';
//Presentational React Component
class Alert extends React.Component {
  componentDidMount () {
    alert(this.props.error);
  }
render() {
    return (
      <div>
      </div>
    )
  }
}
export default Alert

Мы отобразим простое предупреждение - сообщение об ошибке, которое было передано как опора при монтировании компонента.

Последний шаг - импортировать этот компонент Alert и передать опору ошибки.

Вернувшись в Streams.js, добавьте импорт:

import Alert from  '../presentationals/Alert';

Затем вложите этот компонент в условный рендеринг, передав нашу переменную error как опору ошибки:

status === "error" ? (
  <div>
    <Alert error = { error } />
  </div>
)

Чтобы проверить это, мы можем изменить URL-адрес запроса API на что-то фиктивное:

axios.get('give me an error!')

Обязательно сохраните где-нибудь фактический URL-адрес запроса API.

Если вы проверите локальный хост, теперь мы должны увидеть предупреждающее сообщение при загрузке страницы:

Идеально!

Вставьте обратно в исходный URL-адрес запроса API:

axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')

Мы завершили наше первое асинхронное приложение с использованием React и Redux!

Окончательный код

Доступно на GitHub.

Заключительные мысли

Теперь мы должны чувствовать себя более комфортно, используя Redux в реальном приложении React. Тем не менее, мы определенно можем внести некоторые улучшения, о которых мы поговорим позже.

Глава 5

Глава 5 уже доступна.

Купить официальную электронную книгу

Если вы хотите поддержать автора и получить копию книги в формате PDF, EPUB и / или MOBI, пожалуйста, купите официальную электронную книгу.

Ура,
Майк Мангиаларди