// 意思是你永远不要写出下面这样的代码

const [userList, setUserList] = useState([]);

// ...

// 直接修改了上面通过 useState 返回的 userList
userList[idx].selected = !userList[idx].selected;
setUserList(userList);

可行的解决办法:

// 创建一个拷贝
let userListShell = [...userList]; // 或者 let userListShell = userList.map(shell => shell);
userListShell[idx].selected = !userListShell[idx].selected;
setUserList(userListShell);

Brief

最近在实现一个用户列表的全选功能,具体如下:

  1. 点击全选后所有用户前面的 checkbox(复选框)均变成选中状态,取消同理,所有在选中的 checkbox 变成未选中
  2. 当存在未选中状态的 checkbox 时,全选的 checkbox 需要变成未选中状态
export function Main() {
  const [userList, setUserList] = useState([])
  const [selectAll, setSelectAll] = useState(false)

  const toggleAllSelections = () => {
    // ①
    let userListShell = [...userList]
    userListShell.map(user => user.selected = !selectAll)
    setSelectAll(!selectAll)
    setUserList(userListShell)
  }

  return (
    <div className='flex flex-col bg-white rounded shadow-sm'>
      <div className='flex border border-gray-100 bg-[#f9fafb] h-14 items-center'>
        <div className='flex ml-2 w-20'>
          <input type='checkbox' className='mt-1.5 h-4 w-4' checked={selectAll}
                 onChange={() => toggleAllSelections()}/>
          <label className='ml-1'>全选</label>
        </div>
          <Button content={<img src={ImageRefresh} alt='刷新'/>}></Button>
        </div>
      </div>

      <div className='grow-0 flex flex-col overflow-auto max-h-156'>
        {
          userList.map((user, idx) => {
              let key = user.name.toString() + ' : ' + user.selected.toString()
              return (
                <UserListItem key={key} user={user} onChange={() => {
                  // ②
                  let userListShell = [...userList]
                  userListShell[idx].selected = !userListShell[idx].selected
                  if (userListShell[idx].selected === false) {
                    setSelectAll(false)
                  } else if (userListShell.findIndex(user => user.selected === false) === -1) {
                    setSelectAll(true)
                  }
                  setUserList(userListShell)
                }}/>
              )
            }
          )
        }
      </div>
    </div>
  )
}

需要注意的是 ①,② 两处,在修改之前均按照开头给的方法进行了 state 的更新, 导致后面出现单个用户的复选框不能正常渲染的问题,但是在只修改 ② 的前提下,问题就得到了解决。 这在一定程度上说明单次直接使用 state 本身进行更新可能不会出现渲染的问题, 但是为了避免意料之外的情况,最好还是把 state 当初 readonly 变量进行处理。

https://zh-hans.react.dev/learn/updating-objects-in-state#treat-state-as-read-only

有关操作 State 中的数组:https://zh-hans.react.dev/learn/updating-arrays-in-state