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

点击编辑开启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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:15:31