Попытка переместить элемент из одного массива в другой в состоянии реакции

#arrays #reactjs #race-condition

Вопрос:

Я пытаюсь научиться реагировать на свои летние каникулы. Я воссоздаю карточную игру под названием «Пятница», и в основном мне нужно перемещать «карты», хранящиеся в массивах в разных состояниях. Например, массив для колоды карт и массив для раздачи карт, но я сталкиваюсь с условиями гонки при слишком быстром рисовании карт из-за асинхронного характера установленного состояния. Наконец-то у меня есть то, что, по моему мнению, должно сработать, но каждый раз я добавлял два дубликата. Мой текущий подход-это функция, которая использует оба набора наборов массивов следующим образом:

 function drawCard(fromSet, toSet) {
  fromSet( previous => {
    const[card, ...remainder] = previous;
    sendToTo(card);
    return [...remainder];
    });
  function sendToTo(card) {
    toSet(prev => [...prev, card]);
  }
}    
 

Однако, если я вызову это дважды подряд и проверю состояние после этого, я увижу, что две карты удалены из массива «из», но в массиве » в » теперь есть [card1, card2, card1, card2]

Я добавил ужасное исправление на данный момент, но я бы предпочел сделать это правильно, если это возможно. Мое решение состоит в том, чтобы изменить вызов toSet на этот:

 toSet(prev => {
  if(prev.some(e => e === card))
    return [...prev];
  return [...prev, card];
};
 

Ответ №1:

Это то, что вы ищете? Попробуйте нажать кнопку в этом поле с кодами (https://codesandbox.io/s/muddy-glade-s7eut?file=/src/App.js)

 import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
  const [deck, setDeck] = useState([]);
  const [hand, setHand] = useState([]);
  const [drawing, setDrawing] = useState(false);

  useEffect(() => {
    const exampleDeck = [
      { cardNumber: 1, cardName: "Card1" },
      { cardNumber: 2, cardName: "Card2" },
      { cardNumber: 3, cardName: "Card3" },
      { cardNumber: 4, cardName: "Card4" },
      { cardNumber: 5, cardName: "Card5" }
    ];
    setDeck(exampleDeck);
  }, []);

  const drawCard = () => {
    if (deck.length > 0) {
      setDrawing(true);
      const topCard = deck[0];
      setHand((existingHand) => [...existingHand, topCard]);
      setDeck(
        deck.filter((deckCard) => deckCard.cardNumber !== topCard.cardNumber)
      );
      setDrawing(false);
    }
  };

  return (
    <div className="App">
      <h4>Deck: </h4>
      {deck.length > 0 amp;amp; deck.map((card) => <p>{card.cardName}</p>)}
      <h4>Hand: </h4>
      {hand.length > 0 amp;amp; hand.map((card) => <p>{card.cardName}</p>)}
      <button onClick={drawCard} disabled={drawing}>
        Draw Card
      </button>
    </div>
  );
}
 

Комментарии:

1. Да, почти. Однако вместо того, чтобы нажимать на карту в колоде, вы нажимаете кнопку, которая рисует верхнюю карту колоды. Проблема в том, что если я использую для этого колоду[0] и обновляю два состояния соответственно, но пользователь снова нажимает кнопку, прежде чем состояния будут фактически обновлены, я в конечном итоге дважды вытаскиваю одну и ту же карту и удаляю вторую, которая должна была быть нарисована.

2. @SimonFarrelly хорошо, я только что обновил свой ответ кодовое поле

3. Это должно сделать свою работу! Как семафор psuedo? Спасибо! В ближайшее время я постараюсь прочитать больше о том, как работает setState. Мне не нравится чувство незнания того, как что-то работает, поскольку это заставляет вас догадываться, как с этим взаимодействовать…

4. @SimonFarrelly вы имеете в виду добавление отключенного тега на кнопку? Да, определенно добавьте состояния «загрузка» во время передачи данных, чтобы вы могли предотвратить повторное нажатие пользователем до завершения операции. Я понимаю, что ты имеешь в виду, я такой же, всегда должен понимать это сверху донизу

5. Да, так что все между setDrawing() в основном привязано к одному потоку за раз (не уверен в терминологии в js). Если я правильно понимаю эту идею. Еще раз спасибо!