
本文详解如何在 react 问答应用中,为每个题目独立管理选项的点击状态,实现「点击变色、切换复原」的交互逻辑,避免跨题干扰与选项错位问题。
本文详解如何在 react 问答应用中,为每个题目独立管理选项的点击状态,实现「点击变色、切换复原」的交互逻辑,避免跨题干扰与选项错位问题。
在构建 Quiz 类型的 React 应用时,一个常见且关键的交互需求是:用户点击某道题的某个选项后,该选项高亮(如背景色变化);当用户再次点击同一题的其他选项时,原高亮项恢复默认样式,新选项接替高亮状态。但若状态管理不当(例如全局共享 isClickedIndex),就会导致所有题目共用同一索引,引发「一动全动」「选项顺序错乱」等典型 Bug——这正是原始代码的核心问题。
根本原因在于:isClickedIndex 是一个全局 state,它无法区分不同题目;而 createRandomOptions() 在每次渲染时都重新打乱数组,配合未绑定题目标识的事件处理,使得 DOM 元素与数据索引关系失稳。
✅ 正确解法是:为每道题维护独立的已选答案索引(或值),并通过条件 className 动态控制样式。
✅ 推荐实现方案(状态隔离 + 精准渲染)
首先,将 isClickedIndex 升级为按题目索引组织的对象:
const [selectedIndices, setSelectedIndices] = useState<Record<number, number>>({});
// key: question index, value: selected option index within that question然后,在渲染每道题时,传入其唯一题号(qIndex),并绑定专属点击处理器:
function handleOptionClick(qIndex: number, optIndex: number) {
setSelectedIndices(prev => ({
...prev,
[qIndex]: optIndex
}));
}最后,在 <li> 中通过 selectedIndices[qIndex] === optIndex 判断是否激活:
<li
key={option}
className={`option ${selectedIndices[qIndex] === optIndex ? 'active' : ''}`}
onClick={() => handleOptionClick(qIndex, optIndex)}
>
{option}
</li>⚠️ 注意事项:
- 不要用 index 作为 key:randomOptions 每次重排都会导致 index 变化,React 会错误复用 DOM。应改用稳定标识,如 option(需确保选项内容唯一)或 btoa(option) 做哈希。
- 避免在 map 内调用 createRandomOptions():应在 useEffect 或 useMemo 中预计算随机顺序,保证渲染一致性。
- CSS 样式需明确作用域:推荐使用 CSS Modules 或 BEM 命名,防止 .active 影响全局。
示例 CSS(局部生效):
/* Quiz.module.css */
.option {
padding: 12px 16px;
margin: 4px 0;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.option.active {
background-color: #4f46e5;
color: white;
}? 补充:若需支持「取消选择」(点击已选项即取消)
只需在 handleOptionClick 中增加判断:
function handleOptionClick(qIndex: number, optIndex: number) {
setSelectedIndices(prev => {
if (prev[qIndex] === optIndex) {
const updated = { ...prev };
delete updated[qIndex];
return updated;
}
return { ...prev, [qIndex]: optIndex };
});
}✅ 总结
| 问题现象 | 根本原因 | 解决关键 |
|---|---|---|
| 所有题同时变色 | 全局 isClickedIndex | 每题独立 state(Record<number, number>) |
| 选项位置跳动 | map 内动态打乱 + index key | 预计算随机顺序 + 稳定 key |
| 样式不生效 | CSS 未正确作用于 .active | 检查 CSS 优先级与作用域 |
通过状态粒度下沉与渲染稳定性保障,即可实现专业、可维护的单题单选高亮体验——这也是 React「状态最小化、作用域最细化」设计哲学的典型实践。










