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


В сегодняшней части перевода учебного курса по 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) {
    return (
        <div className="todo-item">
            <input 
                type="checkbox" 
                checked={props.item.completed} 
                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(). Попытайтесь вспомнить о том, как именно он работает. Этот метод позволяет вмешиваться в работу компонента, выполняя некий код сразу после того, как компонент был добавлен в дерево DOM. Когда мы говорили о методах жизненного цикла компонентов, я упоминал о том, что метод componentDidMount() чаще всего используется для загрузки данных из неких внешних источников. Эти данные используются компонентом для реализации его предназначения.

Начнём наши сегодняшние эксперименты с нового проекта, созданного средствами 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! доказывает работоспособность кода, поэтому мы можем продолжать работу. Как уже было сказано, в этом методе обычно загружают данные, необходимые для работы компонента.

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

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

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

В методе 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.

Далее, в методе render(), опираясь на свойство состояния loading, настроим текст, выводимый на страницу. Вот как будет выглядеть код 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({
                    character: data
                })
            })
    }
    
    render() {
        const text = this.state.loading ? "loading..." : this.state.character.name
        return (
            <div>
                <p>{text}</p>
            </div>
        )
    }
}

export default App

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


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

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

Собственно говоря, проблема тут заключается в том, что мы, перед началом загрузки данных, установили loading в true, а после завершения загрузки не записали в loading false. В результате на странице всегда выводится текст loading.... Исправить эту ошибку несложно. Достаточно, там же, где мы записываем в состояние загруженные данные, установить 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-приложения?




К сожалению, не доступен сервер mySQL