Как хранить и обновлять массивы в React useState hook

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

Настройка проекта

Создайте проект react, выполнив следующую команду:

npx create-react-app react-usestate-array
Войти в полноэкранный режим Выйти из полноэкранного режима

Обновите файл index.css следующим кодом для стилизации приложения:

body {
  display: flex;
  justify-content: center;
}

.App {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

ul {
  padding: 0;
}

button {
  margin: 0.5rem;
  cursor: pointer;
}

ul li {
  display: flex;
  align-items: center;
  list-style-type: disc;
  justify-content: space-between;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Обновите файл App.js следующим кодом:

import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)

  return (
    <div className="App">
      <div>
        <button>Add Item to Start</button>
        <button>Add Item to End</button>
        <button>Add Item in between</button>
      </div>
      <div>
        <button>Delete Item from Start</button>
        <button>Delete Item from End</button>
        <button onClick>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment">+</button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь мы создаем список случайных чисел, инициализируем локальное состояние со списком случайных чисел и отображаем их. Против каждого числа в списке у нас есть кнопка для его увеличения. Также у нас есть кнопки для изменения списка.

Изменение элемента в массиве

Сначала давайте сделаем так, чтобы кнопки увеличения работали:

import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)
  const incrementNumber = index => {
    setList(existingItems => {
      return [
        ...existingItems.slice(0, index),
        existingItems[index] + 1,
        ...existingItems.slice(index + 1),
      ]
    })
  }
  return (
    <div className="App">
      <div>
        <button>Add Item to Start</button>
        <button>Add Item to End</button>
        <button>Add Item in between</button>
      </div>
      <div>
        <button>Delete Item from Start</button>
        <button>Delete Item from End</button>
        <button onClick>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment" onClick={() => incrementNumber(i)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
Войти в полноэкранный режим Выход из полноэкранного режима

Как вы, наверное, знаете, мы не должны напрямую изменять состояние.
Поэтому мы используем обратный вызов, который является вторым аргументом функции setList. Обратный вызов получает аргумент, который является существующим состоянием, и мы используем метод slice
и операторы spread, чтобы вернуть обновленный массив.

Альтернативный способ — получить обновленный массив с помощью функции map:

const incrementNumber = index => {
  setList(existingItems => {
    return existingItems.map((item, j) => {
      return j === index ? item + 1 : item
    })
  })
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Здесь внутри функции map мы проверяем, совпадает ли переданный индекс с текущим индексом, затем увеличиваем число на единицу, в противном случае возвращаем прежнее число.

Добавление элементов в массив

Мы рассмотрим, как добавить элемент в начало, конец и где-то между ними массива.

Добавление элемента в начало массива:

Мы можем добавить элемент с помощью оператора spread, как показано ниже:

const addItemToStart = () => {
  setList(existingItems => {
    return [getRandomNumber(), ...existingItems]
    // return [getRandomNumber()].concat(existingItems);
  })
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Как видно из прокомментированного кода, вы также можете использовать метод concat.

Не забудьте привязать функцию addItemToStart к обработчику onClick!

import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)
  const incrementNumber = index => {
    setList(existingItems => {
      return [
        ...existingItems.slice(0, index),
        existingItems[index] + 1,
        ...existingItems.slice(index + 1),
      ]
      // return existingItems.map((item, j) => {
      //   return j === index ? item + 1 : item;
      // });
    })
  }

  const addItemToStart = () => {
    setList(existingItems => {
      return [getRandomNumber(), ...existingItems]
      // return [getRandomNumber()].concat(existingItems);
    })
  }

  return (
    <div className="App">
      <div>
        <button onClick={addItemToStart}>Add Item to Start</button>
        <button>Add Item to End</button>
        <button>Add Item in between</button>
      </div>
      <div>
        <button>Delete Item from Start</button>
        <button>Delete Item from End</button>
        <button onClick>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment" onClick={() => incrementNumber(i)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
Вход в полноэкранный режим Выход из полноэкранного режима

Добавление элемента в конец массива

Аналогично добавлению элемента в начало массива, мы можем использовать оператор spread, изменяя порядок:

const addItemToEnd = () => {
  setList(existingItems => {
    return [...existingItems, getRandomNumber()]
    // return existingItems.concat([getRandomNumber()]);
  })
}
Войти в полноэкранный режим Выход из полноэкранного режима

Альтернативный подход с использованием метода concat также может быть использован, как показано в прокомментированном коде выше.

Добавление элемента между элементами массива

Скажем, если вам нужно добавить элемент в определенный индекс, а затем сдвинуть остальные элементы вправо на 1 индекс, вы можете сделать это с помощью оператора slice и spread, как показано ниже:

const addItemInBetween = () => {
  setList(existingItems => {
    const randomIndex = getRandomNumber(existingItems.length)
    const randomNumber = getRandomNumber()
    return [
      ...existingItems.slice(0, randomIndex),
      randomNumber,
      ...existingItems.slice(randomIndex),
    ]
  })
}
Вход в полноэкранный режим Выйти из полноэкранного режима

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

Мы также можем использовать метод reduce, как показано ниже, для добавления элемента между ними:

const addItemInBetween = () => {
  setList(existingItems => {
    const randomIndex = getRandomNumber(existingItems.length)
    const randomNumber = getRandomNumber()

    return existingItems.reduce(
      (prev, x, i) => prev.concat(i === randomIndex ? [randomNumber, x] : x),
      []
    )
  })
}
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь внутри обратного вызова метода reduce, если индекс совпадает с индексом обновляемого элемента, то мы конкатенируем предыдущий массив с массивом из номера, который нужно вставить, и текущего элемента. В противном случае мы просто конкатенируем текущий элемент с предыдущим массивом.

Удаление элементов из массива

При удалении мы также рассмотрим, как удалять с начала, конца и между элементами массива.

Удаление элемента из начала массива

Здесь мы также можем использовать метод slice. Когда мы передаем 1 в качестве первого аргумента методу slice, он возвращает все элементы, начиная с первого индекса (все элементы, кроме первого, так как индекс массива начинается с 0).

const deleteItemFromStart = () => {
  setList(existingItems => {
    return existingItems.slice(1)
    // return existingItems.filter((item, i) => i !== 0);
  })
}
Вход в полноэкранный режим Выход из полноэкранного режима

Как видите, мы также можем использовать метод filter, где мы проверяем, равен ли индекс 0, и если да, то отфильтровываем его.

Удаление элемента из конца массива

Последний индекс массива можно найти с помощью Array.length - 1, поэтому для удаления последнего элемента мы можем выполнить Array.slice(0, Array.length - 1):

const deleteItemFromEnd = () => {
  setList(existingItems => {
    return existingItems.slice(0, existingItems.length - 1)
    // return existingItems.filter((item, i) => i !== existingItems.length - 1);
  })
}
Вход в полноэкранный режим Выход из полноэкранного режима

Даже функция filter также может быть использована, как показано в прокомментированном коде.

Удаление элемента между элементами массива

При удалении из определенной позиции мы можем использовать комбинацию метода slice и оператора spread:

const removeItemInBetween = () => {
  setList(existingItems => {
    const randomIndex = getRandomNumber(existingItems.length)
    return [
      ...existingItems.slice(0, randomIndex),
      ...existingItems.slice(randomIndex + 1),
    ]

    // return existingItems.reduce(
    //   (prev, x, i) => prev.concat(i === randomIndex ? [] : x),
    //   []
    // );
  })
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

Окончательный код

Вот окончательный код со всеми операциями вместе:

import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)
  const incrementNumber = index => {
    setList(existingItems => {
      return [
        ...existingItems.slice(0, index),
        existingItems[index] + 1,
        ...existingItems.slice(index + 1),
      ]
      // return existingItems.map((item, j) => {
      //   return j === index ? item + 1 : item;
      // });
    })
  }

  const addItemToStart = () => {
    setList(existingItems => {
      return [getRandomNumber(), ...existingItems]
      // return [getRandomNumber()].concat(existingItems);
    })
  }

  const addItemToEnd = () => {
    setList(existingItems => {
      return [...existingItems, getRandomNumber()]
      // return existingItems.concat([getRandomNumber()]);
    })
  }

  const deleteItemFromStart = () => {
    setList(existingItems => {
      return existingItems.slice(1)
      // return existingItems.filter((item, i) => i !== 0);
    })
  }

  const deleteItemFromEnd = () => {
    setList(existingItems => {
      return existingItems.slice(0, existingItems.length - 1)
      // return existingItems.filter((item, i) => i !== existingItems.length - 1);
    })
  }

  const addItemInBetween = () => {
    setList(existingItems => {
      const randomIndex = getRandomNumber(existingItems.length)
      const randomNumber = getRandomNumber()
      return [
        ...existingItems.slice(0, randomIndex),
        randomNumber,
        ...existingItems.slice(randomIndex),
      ]

      // return existingItems.reduce(
      //   (prev, x, i) => prev.concat(i === randomIndex ? [randomNumber, x] : x),
      //   []
      // );
    })
  }

  const removeItemInBetween = () => {
    setList(existingItems => {
      const randomIndex = getRandomNumber(existingItems.length)
      return [
        ...existingItems.slice(0, randomIndex),
        ...existingItems.slice(randomIndex + 1),
      ]

      // return existingItems.reduce(
      //   (prev, x, i) => prev.concat(i === randomIndex ? [] : x),
      //   []
      // );
    })
  }
  return (
    <div className="App">
      <div>
        <button onClick={addItemToStart}>Add Item to Start</button>
        <button onClick={addItemToEnd}>Add Item to End</button>
        <button onClick={addItemInBetween}>Add Item in between</button>
      </div>
      <div>
        <button onClick={deleteItemFromStart}>Delete Item from Start</button>
        <button onClick={deleteItemFromEnd}>Delete Item from End</button>
        <button onClick={removeItemInBetween}>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment" onClick={() => incrementNumber(i)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
Вход в полноэкранный режим Выход из полноэкранного режима

Обновление массива объектов

Рассмотрим приведенный ниже массив:

[
  { "id": 1001, "score": 250 },
  { "id": 1002, "score": 100 },
  { "id": 1003, "score": 300 }
]
Вход в полноэкранный режим Выход из полноэкранного режима

Если у вас есть массив объектов с уникальными идентификаторами, присвоенными каждому объекту, и вы хотите изменить массив на основе идентификатора, то этого можно добиться с помощью следующей функции:

const incrementScore = currentId => {
  setScore(existingItems => {
    const itemIndex = existingItems.findIndex(item => item.id === currentId)
    return [
      ...existingItems.slice(0, itemIndex),
      {
        // spread all the other items in the object and update only the score
        ...existingItems[itemIndex],
        score: existingItems[itemIndex].score + 1,
      },
      ...existingItems.slice(itemIndex + 1),
    ]
  })
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Той же функциональности можно достичь с помощью функции map, как показано ниже:

const incrementScore = currentId => {
  setScore(existingItems => {
    return existingItems.map(item => {
      return item.id === currentId ? { ...item, score: item.score + 1 } : item
    })
  })
}
Войти в полноэкранный режим Выход из полноэкранного режима

Вот полный код, использующий вышеуказанные функции:

import { useState } from "react"

const INITIAL_SCORES = [
  { id: 1001, score: 250 },
  { id: 1002, score: 100 },
  { id: 1003, score: 300 },
]

function Scores() {
  const [score, setScore] = useState(INITIAL_SCORES)

  const incrementScore = currentId => {
    setScore(existingItems => {
      const itemIndex = existingItems.findIndex(item => item.id === currentId)
      return [
        ...existingItems.slice(0, itemIndex),
        {
          // spread all the other items in the object and update only the score
          ...existingItems[itemIndex],
          score: existingItems[itemIndex].score + 1,
        },
        ...existingItems.slice(itemIndex + 1),
      ]
      //   return existingItems.map((item) => {
      //     return item.id === currentId
      //       ? { ...item, score: item.score + 1 }
      //       : item;
      //   });
    })
  }

  return (
    <div className="App">
      <ul>
        {score.map(item => {
          return (
            <li key={item.id}>
              <span>{item.score}</span>
              <button title="increment" onClick={() => incrementScore(item.id)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default Scores
Вход в полноэкранный режим Выход из полноэкранного режима

Оцените статью
devanswers.ru
Добавить комментарий