Javascript 如果连续单击两个相同的名称,如何使它们保持红色?
我用列表项表示名称,当单击任何名称时,它会变为红色,然后花一秒钟时间再次返回黑色,但连续单击两个相同的名称会使它们保持红色,而不会再次变为黑色 你可以把它想象成一个记忆游戏,但我试着在这里举一个简单的例子,说明我在最初的项目中试图实现的目标 这是我的代码和错误的尝试:Javascript 如果连续单击两个相同的名称,如何使它们保持红色?,javascript,reactjs,Javascript,Reactjs,我用列表项表示名称,当单击任何名称时,它会变为红色,然后花一秒钟时间再次返回黑色,但连续单击两个相同的名称会使它们保持红色,而不会再次变为黑色 你可以把它想象成一个记忆游戏,但我试着在这里举一个简单的例子,说明我在最初的项目中试图实现的目标 这是我的代码和错误的尝试: const App = () => { const { useState } = React; const items = [ { name: 'm
const App = () => {
const { useState } = React;
const items = [
{
name: 'mark',
id: 1,
red: false
},
{
name: 'peter',
id: 2,
red: false
},
{
name: 'john',
id: 3,
red: false
},
{
name: 'mark',
id: 4,
red: false,
},
{
name: 'peter',
id: 5,
red: false
},
{
name: 'john',
id: 6,
red: false
}
];
const [names, setNames] = useState(items);
const [firstName, setFirstName] = useState(null);
const [secondName, setSecondName] = useState(null)
const handleItemClick = (item) => {
setNames(prev => prev.map(i => i.id === item.id ? { ...i, red: true } : i));
//the problem is here
setTimeout(() => {
setNames(prev => prev.map(n => {
if (secondName && (secondName.name === firstName.name) && n.name === firstName.name) {
return { ...n,red: true }
}
return { ...n, red: false };
}))
}, 1000)
if (!firstName) setFirstName(item);
else if (firstName && !secondName) setSecondName(item)
else if (firstName && secondName) {
setFirstName(item);
setSecondName(null)
}
}
return (
<div class="app">
<ul class="items">
{
names.map(i => {
return (
<Item
item={i}
handleItemClick={handleItemClick}
/>
)
})
}
</ul>
</div>
)
}
const Item = ({ item, ...props }) => {
const { id, name, red } = item;
const { handleItemClick } = props;
return (
<li
className={`${red ? 'red' : ''}`}
onClick={() => handleItemClick(item)}
>
{name}
</li>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
const-App=()=>{
const{useState}=React;
常数项=[
{
姓名:'马克',
id:1,
红色:错
},
{
姓名:'彼得',
id:2,
红色:错
},
{
姓名:'约翰',
id:3,
红色:错
},
{
姓名:'马克',
id:4,
瑞德:错,
},
{
姓名:'彼得',
id:5,
红色:错
},
{
姓名:'约翰',
id:6,
红色:错
}
];
const[names,setNames]=useState(项);
const[firstName,setFirstName]=useState(null);
常量[secondName,setSecondName]=useState(null)
常量handleItemClick=(项目)=>{
集合名(prev=>prev.map(i=>i.id==item.id?{…i,红色:true}:i));
//问题就在这里
设置超时(()=>{
集合名(prev=>prev.map(n=>{
if(secondName&(secondName.name==firstName.name)&&n.name==firstName.name){
返回{…n,红色:true}
}
返回{…n,红色:false};
}))
}, 1000)
如果(!firstName)setFirstName(项);
else if(firstName&!secondName)设置secondName(项目)
else if(firstName&&secondName){
setFirstName(项目);
setSecondName(空)
}
}
返回(
{
names.map(i=>{
返回(
)
})
}
)
}
常量项=({Item,…props})=>{
常量{id,name,red}=item;
const{handleItemClick}=props;
返回(
handleItemClick(项目)}
>
{name}
)
}
ReactDOM.render(,document.getElementById('root'))
但该代码不能正常工作,当连续单击两个相同的名称时,它们不会保持红色并再次变黑在我看来,问题似乎是事件处理程序过载,违反了单一责任原则 处理程序应该负责处理click事件,而不负责其他任何事情。在这种情况下,单击元素时,您希望将
id
添加到所选/拾取名称的状态中,并切换具有匹配id
的项目的red
状态值。将超时效果考虑到(奇怪的是)一个useffect
hook中,并将选取作为依赖项。这将使超时逻辑反转为清除/重置状态,而不是设置“红色”与否。您还可以/应该将确定匹配的任何逻辑移到相同的效果中(因为它已经具有依赖关系)
这将允许您简化名称设置逻辑,以
if (!firstName) {
setFirstName(item);
} else {
setSecondName(item);
}
注意:我认为您需要另一个数据结构来保存/跟踪/存储用户进行的现有匹配
工作原理:
firstName
为空并更新,红色状态更新firstName
,因此更新secondName
,更新红色状态id
s数组,该数组仅在选定的id
尚未选定且2个拾取尚未选定时更新
const App = () => {
const [names, setNames] = useState(items);
const [picks, setPicks] = useState([]);
const [matched, setMatched] = useState({});
/**
* On click event, add id to `picks` array, allow only two picks
*/
const onClickHandler = id => () =>
picks.length !== 2 &&
!picks.includes(id) &&
setPicks(picks => [...picks, id]);
/**
* Effect to toggle red state if id is included in current picks
*/
useEffect(() => {
setNames(names =>
names.map(name => ({
...name,
red: picks.includes(name.id)
}))
);
}, [picks]);
/**
* Effect checks for name match, if a match is found it is added to the
* `matched` array.
*/
useEffect(() => {
// matches example: { mark: 1, peter: 0, john: 0 }
const matches = names.reduce((matches, { name, red }) => {
if (!matches[name]) matches[name] = 0;
red && matches[name]++;
return matches;
}, {});
const match = Object.entries(matches).find(([_, count]) => count === 2);
if (match) {
const [matchedName] = match;
setMatched(matched => ({ ...matched, [matchedName]: matchedName }));
}
const timer = setTimeout(() => {
if (picks.length === 2) {
setPicks([]);
setNames(names => names.map(name => ({ ...name, red: false })));
}
}, 1000);
return () => clearTimeout(timer);
}, [names, picks]);
return (
<div className="App">
<ul>
{names.map(item => (
<Item
key={item.id}
item={item}
matches={matched}
onClick={onClickHandler(item.id)}
/>
))}
</ul>
</div>
);
};
const Item = ({ item, matches, ...props }) => {
const { name, red } = item;
return (
<li
className={classnames({
red: red || matches[name], // for red text color
matched: matches[name] // any other style to make matches stand out
})}
{...props}
>
{name}
</li>
);
};
const-App=()=>{
const[names,setNames]=useState(项);
const[picks,setPicks]=useState([]);
const[matched,setMatched]=useState({});
/**
*单击事件时,将id添加到“picks”数组,只允许两次picks
*/
const onClickHandler=id=>()=>
长度!==2&&
!picks.includes(id)&&
setPicks(picks=>[…picks,id]);
/**
*如果当前拾取中包含id,则切换红色状态的效果
*/
useffect(()=>{
集合名称(名称=>
names.map(name=>({
名称
红色:picks.includes(name.id)
}))
);
},[picks]);
/**
*效果检查名称匹配,如果找到匹配项,则将其添加到
*`matched`数组。
*/
useffect(()=>{
//匹配示例:{mark:1,peter:0,john:0}
const matches=names.reduce((匹配,{name,red})=>{
如果(!matches[name])匹配[name]=0;
红色和匹配[名称]+;
返回比赛;
}, {});
const match=Object.entries(matches.find)([[uu,count])=>count==2);
如果(匹配){
常量[matchedName]=匹配;
setMatched(matched=>({…matched,[matchedName]:matchedName}));
}
常量计时器=设置超时(()=>{
if(picks.length==2){
集选([]);
setNames(name=>names.map(name=>({…name,红色:false}));
}
}, 1
const App = () => {
const [names, setNames] = useState(items);
const [picks, setPicks] = useState([]);
const [matched, setMatched] = useState({});
/**
* On click event, add id to `picks` array, allow only two picks
*/
const onClickHandler = id => () =>
picks.length !== 2 &&
!picks.includes(id) &&
setPicks(picks => [...picks, id]);
/**
* Effect to toggle red state if id is included in current picks
*/
useEffect(() => {
setNames(names =>
names.map(name => ({
...name,
red: picks.includes(name.id)
}))
);
}, [picks]);
/**
* Effect checks for name match, if a match is found it is added to the
* `matched` array.
*/
useEffect(() => {
// matches example: { mark: 1, peter: 0, john: 0 }
const matches = names.reduce((matches, { name, red }) => {
if (!matches[name]) matches[name] = 0;
red && matches[name]++;
return matches;
}, {});
const match = Object.entries(matches).find(([_, count]) => count === 2);
if (match) {
const [matchedName] = match;
setMatched(matched => ({ ...matched, [matchedName]: matchedName }));
}
const timer = setTimeout(() => {
if (picks.length === 2) {
setPicks([]);
setNames(names => names.map(name => ({ ...name, red: false })));
}
}, 1000);
return () => clearTimeout(timer);
}, [names, picks]);
return (
<div className="App">
<ul>
{names.map(item => (
<Item
key={item.id}
item={item}
matches={matched}
onClick={onClickHandler(item.id)}
/>
))}
</ul>
</div>
);
};
const Item = ({ item, matches, ...props }) => {
const { name, red } = item;
return (
<li
className={classnames({
red: red || matches[name], // for red text color
matched: matches[name] // any other style to make matches stand out
})}
{...props}
>
{name}
</li>
);
};