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

React使用useRef数组实现Todo列表焦点控制异常排查

React Todo 应用焦点控制问题修复

核心问题排查

你当前实现的Add按钮焦点异常由两个直接原因导致:

  • React状态更新是异步批处理机制,调用setMyRows后不会立刻同步渲染DOM,此时执行focus逻辑根本拿不到新生成的输入框DOM引用
  • 焦点索引计算使用了更新前的旧状态值:首次点击Add时初始列表长度为0,计算出的目标索引为0-1=-1,自然无响应;后续每次点击都取旧数组长度计算索引,焦点永远滞后一位,落在上一次新增的输入框上
  • 上移、下移、删除操作未实现对应焦点控制逻辑,不满足你列出的交互规则

修复后完整实现

import React, { useState, useRef, useEffect } from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";

const App = () => {
    const [myRows, setMyRows] = useState([]);
    const focusInput = useRef([]);
    // 记录需要聚焦的目标索引,DOM更新后统一执行聚焦
    const [focusTarget, setFocusTarget] = useState(null);
    // 生成唯一行ID,避免删除后新增ID重复导致的渲染异常
    const nextId = useRef(0);

    // DOM更新完成后自动执行聚焦
    useEffect(() => {
        if (focusTarget !== null && focusInput.current[focusTarget]) {
            focusInput.current[focusTarget].focus();
            setFocusTarget(null);
        }
    }, [focusTarget, myRows]);

    const onAddRow = () => {
        setMyRows((prevRows) => {
            return [
                ...prevRows,
                { id: nextId.current++, text: "", up: "↑", down: "↓", delete: "X" },
            ];
        });
        // 新增项在列表最后一位,索引等于新增前列表长度
        setFocusTarget(myRows.length);
    };

    const onMoveUp = (index) => () => {
        const currentState = [...myRows];
        if (index !== 0) {
            // 交换相邻项
            [currentState[index - 1], currentState[index]] = [currentState[index], currentState[index - 1]];
            setMyRows(currentState);
            // 上移后原位置-1为目标位置
            setFocusTarget(index - 1);
        } else {
            // 已经在顶部,直接聚焦第一项
            setFocusTarget(0);
        }
    };

    const onMoveDown = (index) => () => {
        const currentState = [...myRows];
        if (index !== myRows.length - 1) {
            // 交换相邻项
            [currentState[index], currentState[index + 1]] = [currentState[index + 1], currentState[index]];
            setMyRows(currentState);
            // 下移后原位置+1为目标位置
            setFocusTarget(index + 1);
        } else {
            // 已经在底部,直接聚焦当前项
            setFocusTarget(index);
        }
    };

    const onDelete = (index) => () => {
        const currentState = [...myRows];
        currentState.splice(index, 1);
        setMyRows(currentState);
        // 删完无剩余项不聚焦
        if (currentState.length === 0) return;
        // 删的是最后一项,聚焦新的最后一项;否则聚焦顶替到当前位置的项
        setFocusTarget(index === currentState.length ? index - 1 : index);
    };

    const onTextUpdate = (index) => (event) => {
        setMyRows((prevState) => {
            const data = [...prevState];
            data[index] = {
                ...data[index],
                text: event.target.value,
            };
            return data;
        });
    };

    return (
        <div className="container">
            <button onClick={onAddRow}>Add</button>
            <br />
            {myRows.map((row, index) => {
                return (
                    <div key={row.id}>
                        <input
                            ref={(el) => (focusInput.current[index] = el)}
                            onChange={onTextUpdate(index)}
                            value={row.text}
                            type="text"
                        />
                        <button onClick={onMoveUp(index)}>{row.up}</button>
                        <button onClick={onMoveDown(index)}>{row.down}</button>
                        <button onClick={onDelete(index)}>{row.delete}</button>
                    </div>
                );
            })}
        </div>
    );
}

ReactDOM.render(<App />, document.getElementById("root"))

关键修改点说明

  • 新增focusTarget状态与配套useEffect:所有操作只需要设置需要聚焦的索引值,等React完成DOM渲染更新后,统一执行聚焦逻辑,彻底解决异步状态更新导致的ref引用不存在、焦点滞后问题
  • 修正所有操作的焦点索引计算逻辑,完全匹配提出的4条焦点规则
  • 替换原有的长度生成ID逻辑为自增唯一ID,避免列表项增删后key重复导致的DOM渲染错位问题
  • 补全上移、下移、删除操作的焦点控制分支,覆盖边界场景(顶部上移、底部下移、删除最后一项、删完列表为空)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 20:57:28