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

React动态用户权限表格嵌套map与状态管理问题

解决方案

1. 性能问题核心修复

你遇到的性能开销高本质不是嵌套map生成14000个checkbox的问题,而是mapPermissions方法内循环调用setChecks导致的上万次重渲染。现代浏览器完全可以承载上万级别的简单dom节点,频繁更新状态才是性能瓶颈。
另外建议将状态结构从数组改为对象映射,查找更新效率从O(n)降到O(1),性能提升非常明显:

// 优先用映射结构,key为`${userId}-${permId}`,直接通过key定位状态
const [checks, setChecks] = useState<Record<string, boolean>>({})

如果一定要用你期望的数组结构也可以,后续渲染和更新逻辑会稍慢,下文也会给出对应实现。

2. 状态初始化逻辑

一次性计算完所有初始状态再调用setChecks,避免循环更新:

useEffect(() => {
  // 等待用户、权限接口都返回数据后再初始化
  if (!users.length || !permissions.length) return
  // 映射结构初始化
  const initChecks: Record<string, boolean> = {}
  users.forEach(user => {
    permissions.forEach(perm => {
      initChecks[`${user.id}-${perm.id}`] = user.group_lists?.includes(perm.id) ?? false
    })
  })
  // 数组结构初始化(如果需要你的目标结构)
  // const initChecks = []
  // users.forEach(user => {
  //   permissions.forEach(perm => {
  //     initChecks.push({
  //       userID: user.id,
  //       permID: perm.id,
  //       checked: user.group_lists?.includes(perm.id) ?? false
  //     })
  //   })
  // })
  setChecks(initChecks)
}, [users, permissions])

3. 表格渲染与checkbox交互修复

直接读取状态里的checked值,不要从原始user数据里计算,解决点击不生效的问题:

// 映射结构对应的渲染逻辑
<TableBody>
  {users.map((user: any) => (
    <TableRow key={user.id}>
      <TableCell className={styles.left}>
        {`${user.first_name} ${user.last_name} (${user.username})`}
      </TableCell>
      {permissions.map((permission: any) => {
        const checkKey = `${user.id}-${permission.id}`
        return (
          <TableCell key={checkKey}>
            <input
              type="checkbox"
              checked={checks[checkKey] ?? false}
              onChange={(e: ChangeEvent<HTMLInputElement>) => {
                setChecks(prev => ({
                  ...prev,
                  [checkKey]: e.target.checked
                }))
              }}
            />
          </TableCell>
        )
      })}
    </TableRow>
  ))}
</TableBody>

如果用你期望的数组结构,对应渲染逻辑如下:

{permissions.map((permission: any) => {
  const checkItem = checks.find(item => item.userID === user.id && item.permID === permission.id)
  return (
    <TableCell key={`${user.id}-${permission.id}`}>
      <input
        type="checkbox"
        checked={checkItem?.checked ?? false}
        onChange={(e: ChangeEvent<HTMLInputElement>) => {
          setChecks(prev => prev.map(item => 
            item.userID === user.id && item.permID === permission.id
              ? {...item, checked: e.target.checked}
              : item
          ))
        }}
      />
    </TableCell>
  )
})}

4. 提交数据逻辑

提交时将状态转换为后端需要的格式即可:

const handleSubmit = async () => {
  // 映射结构转目标格式
  const submitData = Object.entries(checks).map(([key, checked]) => {
    const [userID, permID] = key.split('-').map(Number)
    return {userID, permID, checked}
  })
  // 数组结构直接传checks即可
  // const submitData = checks
  await fetch('你的提交接口地址', {
    method: 'POST',
    body: JSON.stringify(submitData)
  })
}

额外优化建议

如果14000个节点渲染仍然卡顿,可以引入虚拟滚动库(比如react-window)只渲染可视区域的表格行,渲染量级直接降到几十行,性能会有质的提升。如果业务允许单条修改即时提交,也可以点击checkbox时直接调用单条更新接口,不需要全量存储状态。

内容的提问来源于stack exchange,提问作者Rodrigo Vila

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 05:54:03