#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). Если я правильно понимаю эту идею. Еще раз спасибо!