Главная » Хабрахабр » [Перевод] Учебный курс по React, часть 22: седьмой этап работы над TODO-приложением, загрузка данных из внешних источников

[Перевод] Учебный курс по React, часть 22: седьмой этап работы над TODO-приложением, загрузка данных из внешних источников

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

image

→ Часть 1: обзор курса, причины популярности React, ReactDOM и JSX
→ Часть 2: функциональные компоненты
→ Часть 3: файлы компонентов, структура проектов
→ Часть 4: родительские и дочерние компоненты
→ Часть 5: начало работы над TODO-приложением, основы стилизации
→ Часть 6: о некоторых особенностях курса, JSX и JavaScript
→ Часть 7: встроенные стили
→ Часть 8: продолжение работы над TODO-приложением, знакомство со свойствами компонентов
→ Часть 9: свойства компонентов
→ Часть 10: практикум по работе со свойствами компонентов и стилизации
→ Часть 11: динамическое формирование разметки и метод массивов map
→ Часть 12: практикум, третий этап работы над TODO-приложением
→ Часть 13: компоненты, основанные на классах
→ Часть 14: практикум по компонентам, основанным на классах, состояние компонентов
→ Часть 15: практикумы по работе с состоянием компонентов
→ Часть 16: четвёртый этап работы над TODO-приложением, обработка событий
→ Часть 17: пятый этап работы над TODO-приложением, модификация состояния компонентов
→ Часть 18: шестой этап работы над TODO-приложением
→ Часть 19: методы жизненного цикла компонентов
→ Часть 20: первое занятие по условному рендерингу
→ Часть 21: второе занятие и практикум по условному рендерингу
→ Часть 22: седьмой этап работы над TODO-приложением, загрузка данных из внешних источников

Занятие 39. Практикум. TODO-приложение. Этап №7

→ Оригинал

▍Задание

Сейчас Todo-приложение выглядит так, как показано на следующем рисунке.

Страница приложения в браузере

Код компонента TodoItem выглядит так:

import React from "react" function TodoItem(props) onChange={() => props.handleChange(props.item.id)} /> <p>{props.item.text}</p> </div> )
} export default TodoItem

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

▍Решение

Представленную здесь задачу можно решить разными способами. Мы воспользуемся встроенным стилем, который опишем в виде константы completedStyle в коде функционального компонента TodoItem. Тут мы настроим свойства текста fontStyle, color и textDecoration. После этого, пользуясь методикой условного рендеринга, назначим этот стиль элементу <p> в том случае, если выводимое им дело отмечено как завершённое. Определять это будем, ориентируясь на переданное экземпляру компонента свойство, которое доступно в нём как props.item.completed.

Преобразованный код компонента будет выглядеть так:

import React from "react" function TodoItem(props) { const completedStyle = { fontStyle: "italic", color: "#cdcdcd", textDecoration: "line-through" } return ( <div className="todo-item"> <input type="checkbox" checked={props.item.completed} onChange={() => props.handleChange(props.item.id)} /> <p style={props.item.completed ? completedStyle: null}>{props.item.text}</p> </div> )
} export default TodoItem

Вот как изменится внешний вид страницы приложения.

Изменённая страница приложения в браузере

При этом стили применяются при установке и снятии флажков, указывающих на состояние пунктов списка дел.

На этом мы завершаем работу над Todo-приложением.

Занятие 40. Загрузка данных из внешних источников

→ Оригинал

Попытайтесь вспомнить о том, как именно он работает. На занятии, посвящённом методам жизненного цикла компонентов, мы говорили о методе componentDidMount(). Когда мы говорили о методах жизненного цикла компонентов, я упоминал о том, что метод componentDidMount() чаще всего используется для загрузки данных из неких внешних источников. Этот метод позволяет вмешиваться в работу компонента, выполняя некий код сразу после того, как компонент был добавлен в дерево DOM. Эти данные используются компонентом для реализации его предназначения.

Начнём наши сегодняшние эксперименты с нового проекта, созданного средствами create-react-app, файл App.js которого содержит следующий код:

import React, {Component} from "react" class App extends Component { constructor() { super() this.state = {} } render() { return ( <div> Code goes here </div> ) }
} export default App

Опишем в коде компонента App, основанного на классе, метод componentDidMount() и проверим работоспособность полученной конструкции, выведя из этого метода что-нибудь в консоль.

import React, {Component} from "react" class App extends Component { constructor() { super() this.state = {} } componentDidMount() { console.log("Hi!") } render() { return ( <div> Code goes here </div> ) }
} export default App

Вывод в консоль строки Hi! доказывает работоспособность кода, поэтому мы можем продолжать работу. Как уже было сказано, в этом методе обычно загружают данные, необходимые для работы компонента.

На этом занятии мы будем пользоваться несколькими вспомогательными средствами, которые пригодятся нам при загрузке данных в компонент.

Речь идёт об API Fetch, представляющем собой удобный интерфейс для получения ресурсов, основанный на промисах, позволяющий выполнять HTTP-запросы, посредством которых и загружаются данные. Первое из них представляет собой встроенную возможность JavaScript.

Этот проект хорош тем, что им, без особых сложностей, можно пользоваться во фронтенд-приложениях (речь, в частности, идёт об особенностях настройки CORS). Второе средство, которым мы будем пользоваться — это API Star Wars.

Преобразуем код метода к следующему виду: В методе componentDidMount() мы собираемся воспользоваться функцией fetch(), передав ей адрес для загрузки данных, преобразовать эти данные к нужному нам виду, и, для того, чтобы проверить правильность работы системы, вывести эти данные в консоль.

componentDidMount() { fetch("https://swapi.co/api/people/1") .then(response => response.json()) .then(data => console.log(data))
}

Здесь мы загружаем данные о некоем герое фильма, обращаясь к API, после этого преобразуем то, что пришло от сервера, в формат JSON, а потом выводим эти данные в консоль. То, что попало в консоль, представлено на следующем рисунке.

Данные, загруженные из API Star Wars, выведены в консоль

Теперь, после того, как у нас есть данные, нам надо подумать о том, как вывести их на страницу приложения. Как видно, в консоль попал объект с данными о Люке Скайуокере. Местом, которое служит для хранения подобных данных, является состояние компонента. Для того чтобы решить эту задачу, сначала стоит учитывать то, что загруженные извне данные, если их никуда не сохранить, невозможно будет вывести на страницу приложения в браузере. Добавим в состояние компонента новое свойство, character, представленное пустым объектом:

this.state = { character: {}
}

Мы собираемся хранить в этом свойстве объект с описанием персонажа, данные о котором загружены из внешнего источника. Они существуют в виде объекта, поэтому мы, инициализируя состояния, делаем свойство character пустым объектом.

При этом в данном случае то, что хранилось в состоянии до этого, нас не интересует, поэтому мы можем просто передать этому методу объект, содержащий новое представление состояния. После этого в том месте кода метода componentDidMount(), где мы получаем данные, мы запишем их в состояние, воспользовавшись методом setState(). В итоге мы приходим к такому коду метода componentDidMount():

componentDidMount() { fetch("https://swapi.co/api/people/1") .then(response => response.json()) .then(data => { this.setState({ character: data }) })
}

Для того чтобы проверить правильность работы тех механизмов, которые теперь существуют в коде, выведем в методе render() что-нибудь, что должно присутствовать в состоянии после записи в него загруженных данных. Теперь код файла App.js будет выглядеть так:

import React, {Component} from "react" class App extends Component { constructor() { super() this.state = { character: {} } } componentDidMount() { fetch("https://swapi.co/api/people/1") .then(response => response.json()) .then(data => { this.setState({ character: data }) }) } render() { return ( <div> {this.state.character.name} </div> ) }
} export default App

А вот как будет выглядеть страница приложения в браузере.

Страница приложения в браузере

Вывод на страницу текста Luke Skywalker демонстрирует правильную работу механизмов загрузки данных.

На выполнение всех этих действий уходит очень мало времени. В нашем приложении используется простой запрос, в ответ на который приложение получает небольшой объём данных, которые быстро обрабатываются и выводятся на страницу. Но если бы обращение к удалённому источнику данных проводилось бы с использованием очень медленной линии связи, или API, из которого загружаются данные, медленно отвечало бы на запросы, до того момента, как приложение могло бы вывести эти данные на экран, могло бы пройти немало времени. Поэтому данные на экран выводятся так быстро, что у нас возникает такое впечатление, будто компонент, сразу после вывода его на экран, уже их в себе содержит. Если подобное встречается в реальных приложениях, это сбивает с толку их пользователей, которые могут решить, что такие приложения работают неправильно. Всё это время экран оставался бы пустым. Это не относится к нашей сегодняшней теме, но именно здесь уместно будет это обсудить. Для того чтобы предусмотреть подобную ситуацию, нужно, во время загрузки и обработки данных, показывать пользователю соответствующее сообщение.

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

Назовём его loading и инициализируем его значением false. Добавим в состояние новое свойство, указывающее на то, выполняется ли в некий момент времени загрузка данных. После этого, сразу перед тем, как выполнять загрузку данных с использованием fetch(), запишем в это свойство true.

Вот как будет выглядеть код App.js после этих преобразований. Далее, в методе render(), опираясь на свойство состояния loading, настроим текст, выводимый на страницу.

import React, {Component} from "react" class App extends Component { constructor() { super() this.state = { loading: false, character: {} } } componentDidMount() { this.setState({loading: true}) fetch("https://swapi.co/api/people/1") .then(response => response.json()) .then(data => { this.setState({ character: data }) }) } render() { const text = this.state.loading ? "loading..." : this.state.character.name return ( <div> <p>{text}</p> </div> ) }
} export default App

Этот код, правда, работает неправильно. А именно, вот как теперь выглядит страница приложения.

Страница приложения в браузере

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

В результате на странице всегда выводится текст loading.... Собственно говоря, проблема тут заключается в том, что мы, перед началом загрузки данных, установили loading в true, а после завершения загрузки не записали в loading false. Достаточно, там же, где мы записываем в состояние загруженные данные, установить loading в значение false. Исправить эту ошибку несложно. В результате код App.js приобретёт следующий вид:

import React, {Component} from "react" class App extends Component { constructor() { super() this.state = { loading: false, character: {} } } componentDidMount() { this.setState({loading: true}) fetch("https://swapi.co/api/people/1") .then(response => response.json()) .then(data => { this.setState({ loading: false, character: data }) }) } render() { const text = this.state.loading ? "loading..." : this.state.character.name return ( <div> <p>{text}</p> </div> ) }
} export default App

Теперь при загрузке данных ненадолго появляется надпись loading..., а после этого на страницу выводится имя персонажа.

Итоги

На этом занятии вы завершили работу над Todo-приложением и узнали о том, как, пользуясь методом жизненного цикла компонента componentDidMount() и стандартным API Fetch, загружать данные из внешних источников, обрабатывать их и выводить на страницы. Кроме того, здесь мы поговорили о реализации механизма оповещения пользователя о выполнении приложением операций, которые могут занимать достаточно много времени. В следующий раз поговорим о формах.

Уважаемые читатели! Как вы загружаете данные из внешних источников в React-приложения?


Оставить комментарий

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

*

x

Ещё Hi-Tech Интересное!

Киберпреступники пять месяцев контролировали ASUS Live Update

Злоумышленники разместили на сервере вредоносный файл с бэкдором, подписанный валидным сертификатом ASUS. Как сообщает «Лаборатория Касперского», хакеры из APT-группировки ShadowHammer 5 месяцев контролировали сервис обновлений ASUS Live Update и заразили более полумиллиона компьютеров по всему миру.Исследователи из «Лаборатории Касперского» обнаружили, ...

Kubernetes 1.14: обзор основных новшеств

14. Этой ночью состоится очередной релиз Kubernetes — 1. По сложившейся для нашего блога традиции, рассказываем о ключевых изменениях в новой версии этого замечательного Open Source-продукта. 14 и сооветствующих issues, pull requests, Kubernetes Enhancement Proposals (KEP). Информация, использованная для подготовки ...