点击编辑开启td的contentEditable后如何用动态useRef实现td聚焦
问题根源
你的自动聚焦不生效是几个核心问题导致的:
- 只声明了单个useRef,没法对应动态列表渲染出来的多行td节点,根本拿不到需要聚焦的目标DOM
- React状态更新是异步批量执行的,你刚调用setState修改编辑状态时,td的contentEditable属性还没挂载到DOM上,此时调用focus不会生效
- 监听编辑状态的useEffect是空实现,没有任何聚焦逻辑
- 额外小bug:非编辑状态下contentEditable传null是不对的,会导致DOM残留异常属性,应该传false
修复后完整代码
import React from "react"; import { useEffect, useState, useRef } from "react"; export const Table = (props) => { const [isEditing, setIsEditing] = useState(false) const [editingRowId, setEditingRowId] = useState(null); // 用对象存所有行可编辑td的DOM引用,用row.id做key匹配 const tdRefs = useRef({}); const handleEditClick = (id) => { // 点击当前行按钮切换编辑/保存状态,点击其他行直接切换到新行编辑 if (editingRowId === id) { setIsEditing(prev => !prev); } else { setEditingRowId(id); setIsEditing(true); } } // 等编辑状态更新、DOM渲染完成后执行聚焦 useEffect(() => { if (isEditing && editingRowId !== null) { const targetTd = tdRefs.current[editingRowId]; if (targetTd) { targetTd.focus(); // 额外把光标定位到内容末尾,不用用户手动点到最后输入 const range = document.createRange(); const selection = window.getSelection(); range.selectNodeContents(targetTd); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); } } }, [isEditing, editingRowId]) return ( <div> <table> <thead> <tr> <td>ID</td> <td>Title</td> <td style={{ paddingRight: "20px" }}>Operation</td> </tr> </thead> <tbody> {props.tableData?.map((row) => { return ( <tr key={row.id}> <td>{row.id}</td> <td ref={el => tdRefs.current[row.id] = el} contentEditable={editingRowId === row.id && isEditing} suppressContentEditableWarning={true} > {row.title} </td> <td style={{ paddingRight: "20px", cursor: "pointer" }} onClick={() => handleEditClick(row.id)} > {editingRowId === row.id && isEditing ? "Save" : "Edit"} </td> <td style={{ cursor: "pointer" }} onClick={() => props.deleteRowData(row.id)} > Delete </td> </tr> ) })} </tbody> </table> </div> ) }
关键改动说明
- 用ref对象做映射表存储所有动态渲染的td节点,每一行渲染时把对应td的DOM实例存在
tdRefs.current[row.id]下,解决动态列表的ref匹配问题 - 补全useEffect逻辑:依赖编辑状态和当前编辑行id,等React完成DOM更新后,直接取对应td节点执行focus,额外加了光标定位到内容末尾的优化,编辑体验更好
- 修正contentEditable属性传值:非编辑状态直接返回false,避免DOM属性异常
- 优化交互逻辑:编辑状态下按钮文字切换为Save,点击其他行编辑时会自动退出上一行的编辑状态,不需要手动关闭
- 去掉了原代码里无用的事件参数,简化逻辑
补充提示:如果需要保存编辑后的内容,记得给可编辑td绑定
onInput或者onBlur事件,把输入的新值同步回父组件传入的tableData状态,否则编辑状态切换后输入的内容会丢失。
内容的提问来源于stack exchange,提问作者Supriya Kumari
相关产品推荐
相关产品推荐

