В сегодняшней части перевода учебного курса по React вам предлагается продолжить работу над Todo-приложением и сделать так, чтобы щелчки по флажкам воздействовали бы на состояние компонента.
→ Часть 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-приложением
Занятие 33. Практикум. TODO-приложение. Этап №6
→ Оригинал
▍Задание
На этом практическом занятии мы продолжим работу над Todo-приложением, сделаем так, чтобы действия пользователя влияли бы на состояние компонента. Речь идёт о том, чтобы у нас появилась возможность отмечать пункты списка дел как выполненные или невыполненные. Ниже приведён код компонента App
, некоторые заготовки и комментарии, имеющиеся в котором, призваны помочь вам в выполнении задания. Собственно говоря, вот что вам предлагается сегодня сделать:
- Создайте в компоненте
App
обработчик события, который реагирует на изменения флажков (речь идёт о событииonChange
) и соответствующим образом меняет состояние приложения. Пожалуй, это — самая сложная часть сегодняшнего задания. Для того чтобы с ней справиться — обратите внимание на комментарии и заготовки, представленные в коде. - Передайте соответствующий метод компоненту
TodoItem
. - В компоненте
TodoItem
создайте механизм, который, при возникновении событияonChange
, вызывает переданный экземпляру компонента метод и передаёт ему идентификатор (id
) дела, которому соответствует флажок, по которому щёлкнул пользователь.
Вот код компонента App
:
import React from "react"
import TodoItem from "./TodoItem"
import todosData from "./todosData"
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
// Обновите состояние так, чтобы у элемента с заданным id свойство
// completed поменялось бы c false на true (или наоборот).
// Помните о том, что предыдущую версию состоянию менять не следует.
// Вместо этого нужно вернуть новую версию состояния, содержащую изменения.
// (Подумайте о том, как для этого использовать метод массивов map.)
}
render() {
const todoItems = this.state.todos.map(item => <TodoItem key={item.id} item={item}/>)
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App
▍Решение
Для начала создадим простой механизм проверки вызова метода handleChange()
. А именно — приведём его к такому виду:
handleChange(id) {
console.log("Changed", id)
}
Теперь мы реализуем то, что нужно сделать в соответствии с пунктами 2 и 3 задания. То есть — создадим связь между щелчком по флажку и вызовом метода handleChange()
с передачей ему id
этого флажка.
Для того чтобы передать экземпляру компонента TodoItem
ссылку на handleChange()
, мы можем поступить так же, как поступали передавая ему свойства и переписать код создания списка компонентов так:
const todoItems = this.state.todos.map(item => <TodoItem key={item.id} item={item} handleChange={this.handleChange}/>)
Обратите внимание на то, что свойство handleChange
, которое будет доступно компонентам TodoItem
, содержит ссылку на метод handleChange
экземпляра компонента App
. В компоненте TodoItem
к этому методу можно обратиться так же, как и к другим передаваемым ему свойствам. На данном этапе работы код TodoItem
выглядит так:
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
Для вызова метода handleChange
в коде компонента можно воспользоваться конструкцией вида props.handleChange()
. При этом данному методу нужно передать id
элемента. Обработчик события onChange
принимает объект события. Нам, для вызова метода handleChange()
, этот объект не требуется. Перепишем код, в котором мы назначаем элементу обработчик события onChange
, так:
onChange={(event) => props.handleChange(props.item.id)}
Здесь мы вызываем метод handleChange()
, передавая ему идентификатор элемента, взятый из переданных ему свойств, из другой функции, которая принимает объект события. Так как мы этот объект здесь не используем, код можно переписать так:
onChange={() => props.handleChange(props.item.id)}
Теперь попробуем запустить приложение и, открыв консоль, пощёлкать по флажкам.
Проверка метода handleChange()
В консоль попадают сообщения, содержащие идентификаторы флажков, по которым мы щёлкаем. Но флажки пока не меняют внешний вид, так как в методе handleChange()
ещё не реализован механизм изменения состояния компонента. В результате мы только что справились со второй и третьей частями задания и теперь можем приступить к работе над его первой частью, пожалуй, самой интересной из всех, касающейся работы с состоянием.
Для начала нам нужно решить вопрос, касающийся того, что в состоянии хранится массив, некий элемент которого, в ответ на щелчок по флажку, должен претерпеть изменения, но мы при этом не должны модифицировать массив, хранящийся в старой версии состояния. То есть, например, нельзя просто пройтись по уже имеющемуся в состоянии массиву объектов, найти нужный элемент и изменить его свойство completed
. Нам нужно, чтобы, после изменения состояния, был бы сформирован новый массив, один из элементов которого будет изменён, а остальные останутся такими же, какими были раньше. Одним из подходов к формированию такого массива является использование метода массивов map()
, упомянутого в комментариях к заданию. Код мы будем писать в методе setState()
. Приведём код метода handleChange()
из компонента App
к следующему виду:
handleChange(id) {
this.setState(prevState => {
})
}
Теперь, с помощью метода map()
, пройдёмся по массиву prevState.todos
и поищем в нём нужный нам элемент, то есть тот, id
которого передано методу handleChange()
, после чего изменим его свойство completed
. Метод map()
возвращает новый массив, который и будет использоваться в новом состоянии приложения, поэтому мы запишем этот массив в константу. Вот как это выглядит:
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
}
Здесь, в ходе обработки массива с помощью map()
, если обнаруживается элемент, id
которого равен id
, переданному методу handleChange()
, значение свойства completed
этого элемента меняется на противоположное (true
на false
и наоборот). После этого, независимо от того, был ли изменён элемент, map()
возвращает этот элемент. Он попадает в новый массив (представленный здесь константой updatedTodos
) под тем же индексом, под которым соответствующий элемент был в массиве todos
из предыдущей версии состояния. После того, как будет просмотрен весь массив и будет полностью сформирован массив updatedTodos
, этот массив используется в качестве значения свойства todos
объекта, возвращаемого методом setState()
, который представляет собой новую версию состояния.
Если запустить приложение теперь, то можно обнаружить, что флажки реагируют на наши воздействия. Это говорит о том, что щелчки по ним меняют состояние приложения, после чего производится их повторный рендеринг с использованием новых данных.
Итоги
Сегодня в нашем распоряжении оказалось работающее Todo-приложение, при написании которого было использовано множество изученных нами концепций React. На самом деле, его ещё вполне можно доработать, в частности, стилизовать его и расширить его возможности. Мы ещё вернёмся к нему на одном из следующих занятий.
Уважаемые читатели! Справились ли вы с сегодняшней практической работой?
Автор: ru_vds