You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 07:57:27