React列表项点击按钮切换d-none类名的实现问题咨询
解答
不是必须拆分组件才能实现这个交互,但把单个学生项拆成自带状态的独立组件,是更推荐的写法,两种方案都能实现需求:
方案一:不拆组件,父组件统一管理展开状态
不需要改动现有渲染结构,直接在父组件state里存储所有展开项的id即可。
首先给主组件state新增一个存展开id的字段,用Set做去重处理最方便:
this.state = { // 保留原有query、students等字段 expandedIds: new Set() }
然后新增切换展开状态的方法:
toggleGradeDisplay = (studentId) => { this.setState(prev => { const nextExpandedSet = new Set(prev.expandedIds); nextExpandedSet.has(studentId) ? nextExpandedSet.delete(studentId) : nextExpandedSet.add(studentId); return {expandedIds: nextExpandedSet} }) }
渲染列表时,判断当前学生id是否在expandedIds集合中,动态给成绩容器增删d-none类,同时给+按钮绑定点击事件:
const students = filteredStudents.map(i => { const isGradeShow = this.state.expandedIds.has(i.id); return ( <div key={i.id} className='d-flex border-bottom'> <img src={i.pic} className='align-self-center border rounded-circle mx-4' /> <div className='my-3'> <h2 className='fw-bold'>{i.firstName.toUpperCase()} {i.lastName.toUpperCase()}</h2> <div className='text-secondary ms-4'> <p className='mb-0'>Email: {i.email}</p> <p className='mb-0'>Company: {i.company}</p> <p className='mb-0'>Skill: {i.skill}</p> <p className='mb-0'>Average: {getAverageScore(i.grades)}%</p> <div className={`mt-2 ${isGradeShow ? '' : 'd-none'}`}> {getGrades(i.grades)} </div> </div> </div> {/* +按钮 */} <button onClick={() => this.toggleGradeDisplay(i.id)}> {isGradeShow ? '-' : '+'} </button> </div> ) });
这个写法的缺点是,每次点击任意一个学生的展开/收起按钮,整个学生列表都会触发重渲染,学生列表量级大时会有性能损耗。
方案二:拆分为独立学生项组件(推荐)
把单个学生的渲染、交互逻辑全抽离到单独组件中,每个组件自己维护自身的展开状态,互不干扰。
单个学生项组件参考实现:
// 如果项目支持React Hooks,也可以写成函数组件+useState,更简洁 class StudentItem extends React.Component { state = { isGradeShow: false } toggleGrade = () => this.setState(prev => ({isGradeShow: !prev.isGradeShow})) render() { const { studentInfo } = this.props; const { isGradeShow } = this.state; return ( <div className='d-flex border-bottom'> <img src={studentInfo.pic} className='align-self-center border rounded-circle mx-4' /> <div className='my-3'> <h2 className='fw-bold'>{studentInfo.firstName.toUpperCase()} {studentInfo.lastName.toUpperCase()}</h2> <div className='text-secondary ms-4'> <p className='mb-0'>Email: {studentInfo.email}</p> <p className='mb-0'>Company: {studentInfo.company}</p> <p className='mb-0'>Skill: {studentInfo.skill}</p> <p className='mb-0'>Average: {getAverageScore(studentInfo.grades)}%</p> <div className={`mt-2 ${isGradeShow ? '' : 'd-none'}`}> {getGrades(studentInfo.grades)} </div> </div> </div> <button onClick={this.toggleGrade}>{isGradeShow ? '-' : '+'}</button> </div> ) } }
主组件渲染时直接遍历生成StudentItem即可,不需要再处理展开相关逻辑:
const items = filteredStudents.map(student => <StudentItem key={student.id} studentInfo={student} /> )
这个写法的优势很明显:单个学生项的交互逻辑完全内聚,代码可维护性更高;点击展开/收起时只会触发当前单个学生项的重渲染,长列表场景下性能优势明显;后续要给学生项加编辑、删除等其他交互时,不需要改动父组件逻辑。
内容的提问来源于stack exchange,提问作者lisov1y
相关产品推荐
相关产品推荐

