Хабрахабр

[Перевод] Учебный курс по React, часть 17: пятый этап работы над TODO-приложением, модификация состояния компонентов

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

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-приложением, модификация состояния компонентов

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

→ Оригинал

▍Задание

Запуская наше Todo-приложение, вы могли заметить, что в консоль выводится уведомление, которое указывает на то, что мы, настроив свойство checked элемента в компоненте TodoItem, не предусмотрели механизм для взаимодействия с этим элементом в виде обработчика события onChange. При работе с интерфейсом приложения это выражается в том, что флажки, выводимые на странице, нельзя устанавливать и снимать.

Здесь вам предлагается оснастить элемент типа checkbox компонента TodoItem обработчиком события onChange, который, на данном этапе работы, достаточно представить в виде функции, которая что-то выводит в консоль.

▍Решение

Вот как сейчас выглядит код компонента TodoItem, который хранится в файле TodoItem.js:

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

Вот что выводится в консоль при запуске приложения.

Уведомление в консоли

При этом флажки на наши воздействия не реагируют.

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

import React from "react" function TodoItem(props) { return ( <div className="todo-item"> <input type="checkbox" checked={props.item.completed} onChange={() => console.log("Changed!")} /> <p>{props.item.text}</p> </div> )
} export default TodoItem

Здесь мы, в качестве обработчика, используем простую функцию, которая выводит в консоль слово Checked!. При этом щелчки по флажкам не приводят к изменению их состояния, но уведомление из консоли, как можно видеть на следующем рисунке, исчезает.

Флажки всё ещё не работают, но уведомление из консоли исчезло

Это небольшое изменение, внесённое в приложение, позволит нам, после того, как мы разберёмся с изменением состояния компонентов, сделать так, чтобы флажки работали правильно.

Занятие 32. Изменение состояния компонентов

→ Оригинал

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

import React from "react" class App extends React.Component { constructor() { super() this.state = { count: 0 } } render() { return ( <div> <h1>{this.state.count}</h1> <button>Change!</button> </div> ) }
} export default App

В файле стилей index.css, который подключён в файле index.js, содержится следующее описание стилей:

div { display: flex; flex-direction: column; align-items: center; justify-content: center;
} h1 { font-size: 3em;
} button { border: 1px solid lightgray; background-color: transparent; padding: 10px; border-radius: 4px; } button:hover { cursor: pointer;
} button:focus { outline:0;
}

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

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

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

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

В методе render() мы выводим заголовок первого уровня, представляющий значение свойства count из состояния компонента, а также кнопку со словом Change!. В нём мы, как всегда, вызываем метод super() и инициализируем состояние, записывая в него свойство count и присваивая ему начальное значение 0. Всё это отформатировано с помощью стилей.

Нам же нужно чтобы щелчок по кнопке менял бы состояние компонента, воздействуя на его свойство count. Если, на данном этапе работы над приложением, открыть его в браузере и щёлкнуть по кнопке, то ничего, естественно, не произойдёт. При этом мы уже изучили методику обработки событий в React, и наша задача сводится к тому, чтобы создать механизм, который, реагируя на щелчок по кнопке, меняет свойство состояния count.

Приступим к решению нашей задачи, оснастив кнопку обработчиком события onClick, который, для начала, будет просто выводить что-нибудь в консоль.

Назвать его можно как угодно, но подобные методы принято называть так, чтобы их имена указывали бы на обрабатываемые ими события. Для этого мы добавим в класс компонента новый метод. Вот как теперь будет выглядеть код компонента App. В результате мы, так как мы собираемся с его помощью обрабатывать событие click, назовём его handleClick().

import React from "react" class App extends React.Component { constructor() { super() this.state = { count: 0 } } handleClick() { console.log("I'm working!") } render() { return ( <div> <h1>{this.state.count}</h1> <button onClick={this.handleClick}>Change!</button> </div> ) }
} export default App

Обратите внимание на то, что обращаясь к этому методу из render(), мы используем конструкцию вида this.handleClick.

Теперь, если щёлкнуть по кнопке, в консоль попадёт соответствующее сообщение.

Щелчок по кнопке вызывает метод класса

Может быть, попробовать поменять состояние компонента напрямую, в методе handleClick()? Сейчас давайте сделаем так, чтобы щелчок по кнопке увеличивал бы число, выводимое над ней, то есть, модифицировал бы состояние компонента. Скажем, что если переписать этот метод так:

handleClick() { this.state.count++
}

Нужно сразу сказать, что так с состоянием компонентов в React не работают. Попытка выполнения подобного кода вызовет ошибку.

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

Component. Возможно, вы помните о том, что мы говорили о специальном методе, используемом для модификации состояния, доступном в компонентах, основанных на классах благодаря тому, что они расширяют класс React. Его используют в тех случаях, когда нужно изменить состояние компонента. Это — метод setState(). Этим методом можно пользоваться по-разному.

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

handleClick() { this.setState({ count: 1 })
}

Попытка воспользоваться таким методом вызовет такую ошибку: TypeError: Cannot read property 'setState' of undefined. На самом деле, то о чём мы сейчас говорим, вызывает множество споров в среде React-разработчиков, и сейчас я собираюсь показать вам очень простой способ решения этой проблемы, который, на первый взгляд, может показаться необычным.

Делается это в конструкторе. Речь идёт о том, что каждый раз, создавая метод класса (handleClick() в нашем случае), в котором планируется использовать метод setState(), этот метод нужно связать с this. Код компонента после этой модификации будет выглядеть так:

import React from "react" class App extends React.Component { constructor() { super() this.state = { count: 0 } this.handleClick = this.handleClick.bind(this) } handleClick() { this.setState({ count: 1 }) } render() { return ( <div> <h1>{this.state.count}</h1> <button onClick={this.handleClick}>Change!</button> </div> ) }
} export default App

Теперь после нажатия на кнопку Change! над ней появится число 1, сообщений об ошибках выводиться не будет.

Нажатие на кнопку модифицирует состояние

После первого щелчка по ней 0 меняется на 1, а если щёлкнуть по ней ещё раз — ничего уже не произойдёт. Правда, кнопка у нас получилась «одноразовой». Код, вызываемый при щелчке по кнопке, делает своё дело, каждый раз меняя состояние на новое, правда, после первого же щелчка по кнопке новое состояние, в котором в свойстве count хранится число 1, не будет отличаться от старого. В общем-то, это и неудивительно. Для того чтобы решить эту проблему, рассмотрим ещё один способ работы с методом setState().

Но часто бывает так, что новое состояние компонента зависит от старого. Если нас не интересует то, каким было предыдущее состояние компонента, то этому методу можно просто передать объект, который заменит состояние. В случаях, когда для изменения состояния нужно быть в курсе того, что в нём хранилось ранее, методу setState() можно передать функцию, которая, в качестве параметра, получает предыдущую версию состояния. В нашем случае это означает, что мы, опираясь на значение свойства count, которое хранится в предыдущей версии состояния, хотим прибавить к этому значению 1. Заготовка этой функции будет выглядеть так: Назвать этот параметр можно как угодно, в нашем случае это будет prevState.

handleClick() { this.setState(prevState => { })
}

Можно подумать, что в подобной функции достаточно просто обратиться к состоянию с помощью конструкции вида this.state, но такой подход нас не устроит. Поэтому важно, чтобы эта функция принимала бы предыдущую версию состояния компонента.

Вот как будет выглядеть метод handleClick(), решающий эту задачу: Функция должна возвращать новую версию состояния.

handleClick() { this.setState(prevState => { eturn { count: prevState.count + 1 } })
}

Обратите внимание на то, что для получения нового значения свойства count мы используем конструкцию count: prevState.count + 1. Можно подумать, что тут подойдёт и конструкция вида count: prevState.count++, но оператор ++ приводит к модификации переменной, к которой он применяется, это будет означать попытку модификации предыдущей версии состояния, поэтому им мы здесь не пользуемся.

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

import React from "react" class App extends React.Component { constructor() { super() this.state = { count: 0 } this.handleClick = this.handleClick.bind(this) } handleClick() { this.setState(prevState => { return { count: prevState.count + 1 } }) } render() { return ( <div> <h1>{this.state.count}</h1> <button onClick={this.handleClick}>Change!</button> </div> ) }
} export default App

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

Каждый щелчок по кнопке увеличивает значение счётчика

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

Если React обнаружит изменение состояния родительского компонента, он выполнит повторный рендеринг дочернего компонента, которому передаётся это состояние. Ранее мы говорили о том, что родительский компонент может, через механизм свойств, передавать свойства из собственного состояния дочерним компонентам. В результате дочерний компонент будет отражать новые данные, хранящиеся в состоянии родительского компонента. Выглядит это как вызов метода render().

Итоги

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

Уважаемые читатели! Как вы относитесь к тому, что состояние компонентов в React нельзя менять напрямую, без использования специальных механизмов?

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть