javascript - 为什么行的顺序在这里很重要?
问题描述
我正在创建一个记忆游戏,但我不明白这两行如何在代码中排序:
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)
在第一种情况下,matched
状态接收到正确的更新,所以为什么相似的卡片匹配,但在第二种情况下,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>
);
};
解决方案
问题 :
这里的主要问题是代码中的以下行位置:
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
在切换订单时获得的值是什么
解决方案 :
希望这能让您的疑问清晰,现在如果我们清楚问题,那么解决方案是什么,这里我们可以通过两种方式解决它,但基本的两种方式都很常见,那就是始终使用更新的卡片值,在这种情况下顺序不再重要
第一个解决方案:
您可以将匹配条件代码块放入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]);
工作演示:
第二个解决方案:(我建议这个)
您可以创建一个useEffect
forcards
值,以便您始终获得更新的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]);
工作演示:
推荐阅读
- modal-dialog - 如何将输入值传递给松弛螺栓框架中模态的下一个视图?
- r - R kable linebreak 切断条纹颜色
- visual-studio-code - 视觉工作室代码建议转义
- authentication - Cakephp 认证结果仅在无状态验证时无效
- firebase - Firebase“实时数据库”和“Cloud Firestore”在 REST API 和身份验证方面的区别
- javascript - 添加时不出现滚动条
- 由jquery动态
- mysql - UNION ALL 具有不同的列数并创建 Nulls MySQL
- mysql - 查询随机排序记录
- mysql - MySQL、HiveQL、SQL:组合集合
- graph-theory - 图编辑距离(GED)和(非)同构图