Реактивность массива объектов в React


Давайте теперь изучим реактивность массива объектов. Как правило, вы будете работать с такой структурой достаточно часто. Поэтому вам необходимо знать, как иммутабельно осуществлять добавление, изменение и удаление элементов такого массива.

Проблема здесь в том, что все изменения следует проводить по id, которые хранятся внутри самих объектов. Из-за этого нельзя просто взять и получить элемент по его id как по ключу массива.

Придется перебирать массив циклом и в цикле проверять каждый из объектов на то, равен ли его id тому, который нам нужен. Если равен, то выполним с ним нужную нам операцию, а если не равен - то оставим элемент без изменения.

Давайте посмотрим на примерах. Пусть у нас есть следующий массив объектов:

const initNotes = [
{
id: 'GYi9G_uC4gBF1e2SixDvu',
prop1: 'value11',
prop2: 'value12',
prop3: 'value13',
},
{
id: 'IWSpfBPSV3SXgRF87uO74',
prop1: 'value21',
prop2: 'value22',
prop3: 'value23',
},
{
id: 'JAmjRlfQT8rLTm5tG2m1L',
prop1: 'value31',
prop2: 'value32',
prop3: 'value33',
},
];

function App() {

}
Давайте выведем каждый элемент нашего массива в отдельном абзаце, а значения свойств каждого объекта - в своем span внутри абзаца:

function App() {
const [notes, setNotes] = useState(initNotes);

const result = notes.map(note => {
return <p key={note.id}>
<span>{note.prop1}</span>,
<span>{note.prop2}</span>,
<span>{note.prop3}</span>
</p>;
});

return <div>
{result}
</div>;
}
Удаление
Пусть в переменной хранится id элемента массива:

const id = 'IWSpfBPSV3SXgRF87uO74';
Давайте удалим элемент с таким id. Используем для этого метод filter:

setNotes(notes.filter(note => {
if (note.id !== id) {
return note;
}
}));
Код можно упростить:

setNotes(notes.filter(note => note.id !== id));

Добавление
Пусть в переменной хранится объект, который мы хотим сделать новым элементом нашего массива:

const newElem = {
id: 'GMNCZnFT4rbBP6cirA0Ha',
prop1: 'value41',
prop2: 'value42',
prop3: 'value43',
};
Для этого можно добавить элемент в копию массива:

const copy = Object.assign([], notes);
copy.push(newElem);
setNotes(copy);
Либо воспользоваться деструтуризацией:

setNotes([...notes, newElem]);

Изменение
Пусть мы хотим изменить какой-нибудь элемент массива. Пусть новые данные хранятся в переменной, например, вот такие:

const data = {
id: 'IWSpfBPSV3SXgRF87uO74',
prop1: 'value21 !',
prop2: 'value22 !',
prop3: 'value23 !',
};
В приведенном объекте id совпадает с id второго элемента массива, а значения свойств - другие. Говоря другими словами в data в свойстве id у нас хранится id того элемента массива, который мы хотим изменить.

Давайте выполним это изменение. Для этого будем перебирать элементы массива циклом и, если id совпадает с искомым, выполним замену элемента, а если не совпадает, оставим элемент без изменений:

setNotes(notes.map(note => {
if (note.id === data.id) {
return data;
} else {
return note;
}
}));
Можно сократить код, воспользовавшись тернарным оператором:

setNotes(notes.map(note => note.id === data.id ? data : note));

Изменение одного свойства
Вам может потребоваться изменять не весь объект, а конкретное свойство. Давайте посмотрим, как это делается.

Пусть в переменных хранятся id элемента, имя свойства для изменения и новое значение свойства:

const id = 'IWSpfBPSV3SXgRF87uO74';
const prop = 'prop1';
const value = '!!!';
Для решения задачи удобно использовать деструктуризацию и вычисляемые имена свойств:

setNotes(notes.map(note => {
if (note.id === id) {
return {...note, [prop]: value};
} else {
return note;
}
}));

Получение элемента
Вам может потребоваться получить элемент массива по его id. Давайте посмотрим, как это делается.

Пусть id элемента хранится в переменной:

const id = 'IWSpfBPSV3SXgRF87uO74';
Давайте получим элемент с таким id. Используем для этого метод reduce:

const result = notes.reduce((res, note) => {
if (note.id === id) {
return note;
} else {
return res;
}
}, {});
Код можно сократить:

const result = notes.reduce((res, note) => note.id === id ? note: res, {});
Получение значение свойства элемента
Вам может потребоваться получить элемент по id, а затем извлечь из этого элемента значение определенного свойства. Давайте посмотрим, как это делается.

Пусть id элемента и необходимое свойство хранятся в переменных:

const id = 'IWSpfBPSV3SXgRF87uO74';
const prop = 'prop1';
Для решения задачи нужно просто модифицировать полученный ранее код с reduce:

const result = notes.reduce((res, note) => {
if (note.id === id) {
return note[prop]; // получаем заданное свойство
} else {
return res;
}
}, '');
А вот сокращенный вариант:

const result = notes.reduce((res, note) => note.id === id ? note[prop] : res, '');

Следующие уроки
В следующих уроках мы будем разбирать реактивность массива объектов подробнее и посвятим отдельные уроки для разбора удаления, добавления, изменения массива объектов.

Для краткости я все примеры буду писать для массива initNotes из начала данного урока, а для всех задач я буду использовать следующий массив продуктов, в котором id() - созданная в предыдущем уроке функция для генерации id:

const initProds = [
{id: id(), name: 'prod1', catg: 'catg1', cost: 100},
{id: id(), name: 'prod2', catg: 'catg2', cost: 200},
{id: id(), name: 'prod3', catg: 'catg3', cost: 300},
];