Связываем компоненты вместе
На данный момент у нас имеются все компоненты, которые нам нужны, пришло время объединять их.
В этом уроке мы должны ответить на несколько вопросов:
Где хранить список?
Каким образом мы добавляем новые пункты?
Как отобразить несколько дел сразу?
Каким образом мы их удаляем?
В этом уроке присутствует повышенная концентрация JavaScript. Я постараюсь объяснить все сложные моменты, а глубже с теорией мы познакомимся в следующей главе.
Где хранить список?
У нас есть два компонента: первый (Field.jsx
) добавляет новые элементы, второй (Item.jsx
) отображает их и имеет возможность удалить. Так как компоненты не должны знать друг о друге, быть максимально изолированными, хранить данные мы будем в ближайшем общем предке, стало быть, TodoApp.jsx
:
export function TodoApp() {
const [items, setItems] = useState([]);
...
Каким образом мы добавляем новые пункты?
Нам нужен способ сообщить в главный компонент (TodoApp
), что произошло нажатие кнопки добавления, и что Field
хочет добавить новую запись в наш список. В Реакте это делается с помощью коллбеков. Коллбек (от англ. call back — "перезвони мне") — это функция, которая передается из родительского компонента в дочерний как параметр, которую дочерний компонент вызывает для того, чтобы сигнализировать родителю о том, что произошло какое-то действие. Давай посмотрим, как мы можем применить это в нашей ситуации.
Для начала добавим Field
в TodoApp
(не забудь импортировать его):
import { Field } from './field';
export function TodoApp() {
//...
return (
<div>
<Field />
</div>
);
}
Создадим функцию, которая будет коллбеком и добавим ее к пропсам Field
:
export function TodoApp() {
const [items, setItems] = useState([]);
function addNewItem() {
}
return (
<div>
<Field onAdd={addNewItem} />
</div>
);
}
Перейдем в Field.jsx
и пропишем этот коллбек как обработчик нажатия кнопки:
export function Field(props) { // не забудь добавить сюда props
const [text, setText] = useState('');
//...
function onButtonClick() {
props.onAdd(text)
}
return (
//...
<button onClick={onButtonClick}>
New
</button>
);
}
(некоторые куски кода были пропущены для краткости)
Что здесь происходит:
При нажатии кнопки вызывается
onButtonClick
onButtonClick
вызывает переданный "сверху"onAdd
и передает ему как первый аргумент значение поля ввода.
Вернемся в TodoApp.jsx
и напишем код, который добавляет элемент в список:
function addNewItem(newItem) {
setItems([...items, newItem]);
}
Это сложная конструкция, давай разберем по частям:
Функция
addNewItem
вызывается, когда мы нажимаем на кнопку добавления.newItem
— новый элемент.Внутри неё мы изменяем значение
items
, теперь оно равно[...items, newItem]
[...items, newItem]
— это способ описать массив, который состоит из всех элементовitems
, а такжеnewItem
.
Как отобразить несколько элементов списка сразу
Теперь, когда у нас есть список, в который мы можем добавлять элементы, нам надо научиться их отображать. Компонент элемента списка, кстати, должен выглядеть примерно так:
export function Item(props) {
return (
<div>
<span>{props.name}</span>
<button>Delete</button>
</div>
);
}
Вернемся в TodoApp
. У нас есть массив items
, надо "сконвертировать" их в <Item name={...}/>
(кстати, не забудь импорировать его) и добавить в дерево. В этом нам поможет функция map
.
Представь, что у тебя есть фабрика с конвейером. Пустые бутылки проходят по конвейеру через аппарат, который наливает в них воду. На входе было 100 пустых бутылок, на выходе — 100 полных бутылок. Так вот твой массив — это бутылки на конвейере, а map — это аппарат.
const array1 = [1,2,3,4]
function f(x) {
return x * x
}
const array2 = array1.map(f)
// array2 = [1,4,9,16]
map
вызывает функциюf
с каждым элементом массива, над которым она была вызвана, и составляет новый массив из результатов выполнения функции.
В нашем случае есть массив строк, нужно из него сделать массив Item
:
function createItem(name) {
return <Item name={name} />;
}
Теперь добавим наши элементы в компонент TodoApp
:
return (
<div className="todo">
<Field onAdd={addNewItem} />
{items.map(createItem)}
</div>
);
Проверь в браузере, что элементы добавляются.
Дополнительная информация
React устроен таким образом, что он пытается произвести как можно меньше манипуляций с веб-страницей для достижения нужного результата. Недостатком этого метода является то, что он иногда "путает" элементы массива и не всегда знает, какой элемент документа соответствует какому элементу массива.
// было:
['привет', 'привет', 'мир']
// стало:
['привет', 'мир']
// какой из элементов массива был удален: первый или второй?
Для этого было придумано "магическое" свойство key
. Правило простое:
Если у нас есть массив React-компонентов, то у каждого из них должен быть свой, уникальный внутри массива,
key
.
Давай разберемся, как им пользоваться.
Мы говорили о том, что map
вызывает функцию f
с каждым элементом массива. Однако, map передает в f
не один, а три аргумента:
Элемент массива
Порядковый номер (индекс) элемента
Сам массив.
Перепишем функцию createItem
так, чтобы она использовала порядковый номер в качестве key
:
function createItem(name, index) {
return <Item name={name} key={index}/>;
}
Удаление элементов
В этой главе мы познакомимся с сестрой функции map
— функцией filter
.
filter
вызывает функциюf
с каждым элементом массива (а также его индексом и самим массивом), над которым она была вызвана, и составляет новый массив из тех элементов исходного массива, для которыхf
вернулаtrue
.
Пример: отфильтровать массив чисел таким образом, чтобы остались только положительные:
const numbers = [0, 1, 2, 3, 4, -1, -2, -3, -4];
function isPositive(number) {
return number > 0
}
const positive = numbers.filter(isPositive)
// positive = [1, 2, 3, 4]
Мы хотим написать функцию, которая обновляет список items, удалив из него элемент с указанным индексом (removedIndex
):
export function TodoApp() {
// ...
function removeItem(removedIndex) {
function f(item, index) {
return removedIndex !== index;
}
setItems(items.filter(f))
}
//return ...
}
Что происходит: filter
вызывает f
c элементами массива, f
в свою очередь, возвращает true
для всех элементов, кроме removedIndex
.
Мы теперь можем дать нашим Item
возможность удалять себя:
function createItem(name, index) {
function remove() {
removeItem(index)
}
return <Item name={name} key={index} onDelete={remove}/>;
}
А Item может запрашивать удаление себя по нажатию кнопки:
export function Item(props) {
return (
<div>
<span>{props.name}</span>
<button onClick={props.onDelete}>
Delete
</button>
</div>
);
}
Проверь в браузере, что элементы добавляются и удаляются.
О, одна небольшая вещь для самостоятельной работы:
Подумай, как сделать так, что поле ввода очищается после нажатия кнопки добавления.
Подумай, как сделать так, чтобы нельзя было добавить пустые строки
Ответ — в конце следующей главы.
Last updated
Was this helpful?