Связываем компоненты вместе
На данный момент у нас имеются все компоненты, которые нам нужны, пришло время объединять их.
В этом уроке мы должны ответить на несколько вопросов:
Где хранить список?
Каким образом мы добавляем новые пункты?
Как отобразить несколько дел сразу?
Каким образом мы их удаляем?
В этом уроке присутствует повышенная концентрация 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>
);
}(некоторые куски кода были пропущены для краткости)
Что здесь происходит:
При нажатии кнопки вызывается
onButtonClickonButtonClickвызывает переданный "сверху"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?