如何在React useState中实现各类数组方法(手风琴场景)
React手风琴组件:基于useState的展开状态数组操作方案
场景与需求
开发手风琴组件,使用如下数据结构:
{ "options": [ { "id": 1, "name": "HELP & INFORMATION", "option": [ "Help", "Track order", "Delivery & returns" ] }, { "id": 2, "name": "ABOUT SLEORPELS", "option": [ "About us", "Career at SLEORPELS", "Corporate responsibility", "Investors' site" ] }, { "id": 3, "name": "MORE FROM SLEORPELS", "option": [ "Mobile and SLEORPELS apps", "SLEORPELS Marketplace", "Gift vouchers", "Black Friday", "SLEORPELS x Thrift+" ] } ] }
需求:点击选项标题(options.name)时展开/收起对应选项列表(options.option),通过useState维护存储展开项id的数组,以此控制展开状态。
核心解决思路
React中useState的状态是不可变的,不能直接对状态数组使用push/splice等修改原数组的方法,必须通过生成新数组来更新状态,触发组件重渲染。以下是对应数组操作的替代实现:
1. 判断id是否已存在(替代includes/indexOf)
直接读取当前状态数组,调用原生includes方法即可:
const isExpanded = expandedIds.includes(item.id);
2. 添加id到数组(替代push)
使用扩展运算符(...)复制原数组,并追加新id,生成新数组后更新状态:
setExpandedIds(prevIds => [...prevIds, id]);
3. 从数组移除id(替代splice/pop)
使用filter方法生成不包含目标id的新数组:
setExpandedIds(prevIds => prevIds.filter(itemId => itemId !== id));
完整组件示例
import { useState } from 'react'; const Accordion = () => { // 初始化展开状态数组,为空表示所有项默认收起 const [expandedIds, setExpandedIds] = useState([]); // 手风琴数据 const accordionData = { "options": [ { "id": 1, "name": "HELP & INFORMATION", "option": ["Help", "Track order", "Delivery & returns"] }, { "id": 2, "name": "ABOUT SLEORPELS", "option": ["About us", "Career at SLEORPELS", "Corporate responsibility", "Investors' site"] }, { "id": 3, "name": "MORE FROM SLEORPELS", "option": ["Mobile and SLEORPELS apps", "SLEORPELS Marketplace", "Gift vouchers", "Black Friday", "SLEORPELS x Thrift+"] } ] }; // 点击标题切换展开状态 const toggleExpand = (id) => { setExpandedIds(prevIds => { if (prevIds.includes(id)) { // 已展开则移除id,收起 return prevIds.filter(itemId => itemId !== id); } else { // 未展开则添加id,展开 return [...prevIds, id]; } }); }; return ( <div className="accordion"> {accordionData.options.map(item => ( <div key={item.id} className="accordion-item"> {/* 标题按钮 */} <button className="accordion-title" onClick={() => toggleExpand(item.id)} > {item.name} <span>{expandedIds.includes(item.id) ? '▼' : '▶'}</span> </button> {/* 展开内容,根据状态控制显示 */} {expandedIds.includes(item.id) && ( <ul className="accordion-content"> {item.option.map((option, idx) => ( <li key={idx}>{option}</li> ))} </ul> )} </div> ))} </div> ); }; export default Accordion;
额外说明
- 使用函数式更新(
setExpandedIds(prevIds => ...))可以确保每次更新都基于最新的状态值,避免因闭包导致的状态不一致问题。 - 如果需要支持同时展开多个项,当前方案完全适用;如果需要仅展开单个项,只需将状态改为存储单个id(而非数组),点击时直接设置为当前id或
null即可。
内容的提问来源于stack exchange,提问作者Primer Cielo
相关产品推荐
相关产品推荐

