Почему здесь важен порядок строк? - PullRequest
0 голосов
/ 16 июня 2020

Я создаю игру памяти и не могу понять, как эти две строки могут упорядочить вопросы в коде:

const timer = setTimeout(() => {
   if (picks.length === 2) {
     //this order that works
     setPicks([])
     setCards(cards => cards.map((c) => ({ ...c, isFlipped: false })));            
   }
}, 500)

const timer = setTimeout(() => {
   if (picks.length === 2) {
     //this order that doesn't work
     setCards(cards => cards.map((c) => ({ ...c, isFlipped: false }))); 
     setPicks([])           
   }
}, 500)

В первом случае * Состояние 1007 * получает правильные обновления, поэтому почему одинаковые карты совпадают, но во втором случае кажется, что состояние matched не получает правильных значений.

Я не могу понять, как порядок этих двух строк влияние matched состояние

const App = () => {
  const icons = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];

  const shuffle = cards => [...cards].sort(() => Math.random() - 0.5);

  const shuffledCards = useMemo(() => {
    return shuffle(icons).map((icon, i) => ({
      icon: icon,
      id: i,
      isFlipped: false
    }));
  });

  const [picks, setPicks] = useState([]);
  const [cards, setCards] = useState(shuffledCards);
  const [matched, setMatched] = useState([]);

  const handleClick = id => {
    !picks.includes(id) && picks.length !== 2 && setPicks([...picks, id]);
  };

  useEffect(() => {
    setCards(cards =>
      cards.map((c, i) =>
        picks.includes(c.id) ? { ...c, isFlipped: true } : c
      )
    );

    const matches = cards.reduce((matches, { icon, isFlipped }) => {
      !matches[icon] && (matches[icon] = 0);
      isFlipped && matches[icon]++;

      return matches;
    }, {});

    Object.entries(matches).forEach(([icon, count]) => {
      count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
    });

    const timer = setTimeout(() => {
      if (picks.length === 2) {
        //the problem is here, that order doesn't work
        setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
        setPicks([]);
      }
    }, 500);
    return () => clearTimeout(timer);
  }, [picks]);

  return (
    <div class="game">
      <Deck
        cards={cards}
        handleClick={handleClick}
        picks={picks}
        matched={matched}
      />
    </div>
  );
};

const Deck = ({ numbers, cards, ...props }) => {
  return (
    <div class="deck">
      {cards.map((c, i) => {
        return <Card i={i} card={c} {...props} />;
      })}
    </div>
  );
};

const Card = ({ handleClick, picks, card, i, matched }) => {
  const { icon, isFlipped, id } = card;

  return (
    <div
      className={`card${isFlipped ? " flipped" : ""} ${
        matched.includes(icon) ? "matched" : ""
      }`}
      onClick={() => handleClick(id)}
    >
      <div class="front" />
      <div class="back">{icon}</div>
    </div>
  );
};

Ответы [ 2 ]

2 голосов
/ 19 июня 2020

ПРОБЛЕМА:

Здесь основная проблема связана с расположением строки ниже в коде :

const matches = cards.reduce((matches, { icon, isFlipped }) => {

Итак, чтобы понять проблему, Во-первых, нам нужно понять, что не так с указанным выше положением строки, пожалуйста, прочтите комментарии, чтобы лучше понять проблему

setCards(cards => // <--------- 1.
    cards.map((c, i) =>  // <--------- 2.
        picks.includes(c.id) ? { ...c, isFlipped: true } : c
    )
);

// 1. current state of cards
// 2. returning updated state of cards

// so, if you are aware of async behavior or setState, then you know
// the below line will always point to snapshot of cards when the useEffect run
// and not to the 1. or 2.

// So here cards is pointing to snapshot when useEffect run 
const matches = cards.reduce((matches, { icon, isFlipped }) => {

Теперь давайте посмотрим на оба случая

 useEffect(() => {

    // Here you will always get the snapshot of cards values, which is currently available

    setCards(cards => // <--------- 1.
        cards.map((c, i) =>  // <--------- 2.
            picks.includes(c.id) ? { ...c, isFlipped: true } : c
        )
    );

    // so if you understand correctly, above code block doesn't make any sense for the below line
    const matches = cards.reduce((matches, { icon, isFlipped }) => {
    ....

    const timer = setTimeout(() => {
        if (picks.length === 2) {
            //this order that works
            setPicks([]) // <---- this will trigger useEffect , and send the cards value from 2.
            setCards(cards => cards.map((c) => ({ ...c, isFlipped: false })));            
        }
    }, 500)

    const timer = setTimeout(() => {
        if (picks.length === 2) {
            //this order that works
            setCards(cards => cards.map((c) => ({ ...c, isFlipped: false })));  // <------------- 3.   
            setPicks([]) // <---- this will trigger useEffect , and send the cards value from 3.
        }
    }, 500)

 },[picks]); // <----- useEffect is dependent on picks

ДЕМО-ОТЛАДКА :

Вы можете изменить порядок в соответствии с вашим случаем, проверить консоль и посмотреть, какие значения вы получаете для cards при изменении порядка

Edit #SO-flip-card-issue


Решение:

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


ПЕРВОЕ РЕШЕНИЕ :

Вы можете поместить блок кода условия соответствия i Помимо setCards(cards => { и работайте с последними значениями карт

  useEffect(() => {
    setCards(cards => {
      const updated = cards.map((c, i) =>
        picks.includes(c.id) ? { ...c, isFlipped: true } : c
      )

      // take whole code block and put it inside the `setCards`
      // and directly work with latest update value
      const matches = updated.reduce((matches, { icon, isFlipped }) => { // <---- HERE
        !matches[icon] && (matches[icon] = 0);
        isFlipped && matches[icon]++;

        return matches;
      }, {});

      Object.entries(matches).forEach(([icon, count]) => {
        count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
      });
      return updated;
    });

    const timer = setTimeout(() => {
      if (picks.length === 2) {
        setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
        setPicks([]);
      }
    }, 500);
    return () => clearTimeout(timer);
  }, [picks]);

РАБОЧАЯ ДЕМО :

Edit #SO-flip-card-solution-1

ВТОРОЕ РЕШЕНИЕ : (и я бы предложил это)

Вы можете создать useEffect для значения cards, чтобы вы всегда получали обновленное значение cards на основе при этом вы можете установить значения соответствия

  useEffect(() => {
    setCards(cards =>
      cards.map((c, i) =>
        picks.includes(c.id) ? { ...c, isFlipped: true } : c
      )
    );

    const timer = setTimeout(() => {
      if (picks.length === 2) {
        setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
        setPicks([]);
      }
    }, 500);
    return () => clearTimeout(timer);
  }, [picks]);

  useEffect(() => {
    const matches = cards.reduce((matches, { icon, isFlipped }) => {
      !matches[icon] && (matches[icon] = 0);
      isFlipped && matches[icon]++;

      return matches;
    }, {});

    Object.entries(matches).forEach(([icon, count]) => {
      count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
    });
  }, [cards]);

РАБОЧАЯ ДЕМО :

Edit #SO-flip-card-solution-2

0 голосов
/ 16 июня 2020

Блок кода, о котором вы спрашиваете, отвечает за сброс состояния карты каждый раз, когда выбираются две карты.

const timer = setTimeout(() => {
    if (picks.length === 2) {
    setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
    setPicks([]);
    }
}, 500);

Состояние выбранных карт picks должно быть сброшено путем передачи ему пустого массива через ловушку setPicks перед состояние карты устанавливается с помощью ловушки setCards поскольку picks может быть установлен только в том случае, если не было сделано ровно 2 выбора. Обнуление длины выбора требуется в текущем logi c, который у вас есть из-за того, что у вас есть в вашем обработчике кликов.

 const handleClick = id => {
    !picks.includes(id) && picks.length !== 2 && setPicks([...picks, id]);
  };
...