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

React状态更新/渲染后如何设置焦点?函数组件Hook场景

解决React函数组件中新增元素后的焦点定位问题

我完全懂你遇到的这个痛点——刚调用setTodos就想立刻聚焦新元素,结果因为React状态更新的异步特性,新DOM还没渲染出来,根本拿不到节点。下面给你一个简洁又可靠的解决方案:

核心思路

利用useEffect监听todos的状态变化,等状态更新完成、组件重新渲染后,再去定位并聚焦新增的<li>元素。这样既避开了状态更新的异步延迟问题,又不用搞复杂的焦点状态跟踪,完美适配你提到的键盘操作场景。

修改后的完整代码

function Todos (props) { 
  const addButton = React.useRef(null) 
  const [todos, setTodos] = React.useState(Immutable.List([])) 
  // 保存上一次的列表长度,用来判断是否是新增了待办项
  const prevTodosLength = React.useRef(todos.size)

  const addTodo = e => { 
    // 用函数式更新更安全,避免闭包导致的旧状态问题
    setTodos(prevTodos => prevTodos.push('Todo text...')) 
  } 

  const updateTodo = (index, value) => { 
    setTodos(todos.set(index, value)) 
  } 

  const removeTodo = index => { 
    setTodos(todos.delete(index)) 
    addButton.current.focus() 
  } 

  // 监听todos变化,处理新增后的焦点定位
  React.useEffect(() => {
    // 只有当列表长度增加时(也就是新增了项)才执行聚焦逻辑
    if (todos.size > prevTodosLength.current) {
      const todoList = document.querySelector('ul')
      const lastTodoItem = todoList.lastElementChild
      if (lastTodoItem) {
        lastTodoItem.focus()
      }
    }
    // 更新上一次的长度记录
    prevTodosLength.current = todos.size
  }, [todos])

  return (
    <div> 
      <button ref={addButton} onClick={addTodo}>Add todo</button> 
      <ul> 
        {todos.map((todo, index) => ( 
          <li 
            key={index}
            tabIndex="0" 
            aria-label={`Todo ${index+1} of ${todos.size}`}
          > 
            <input 
              type="text" 
              value={todo} 
              onChange={e => updateTodo(index, e.target.value)}
            /> 
            <a onClick={e => removeTodo(index)} href="#">Delete todo</a> 
          </li> 
        ))} 
      </ul> 
    </div> 
  )
} 
ReactDOM.render(React.createElement(Todos, {}), document.getElementById('app'))

关键细节解释

  • 异步状态的正确处理:React的setState(包括setTodos)是异步操作,调用后不会立刻更新DOM。useEffect的依赖数组设为todos,确保每次todos更新完成、组件重新渲染后,才执行聚焦逻辑。
  • 避免无效聚焦:通过prevTodosLength记录上一次的列表长度,只有当长度增加时才触发聚焦,这样编辑、删除待办项时不会乱跳焦点。
  • DOM查询的可靠性:组件重新渲染后,新的<li>已经存在于DOM中,此时用document.querySelector('ul').lastElementChild能准确拿到新增的项,调用focus()就能完成焦点定位。
  • Immutable的最佳实践:修改了addTodo里的setTodos写法,用函数式更新prevTodos => prevTodos.push(...),避免闭包导致获取到旧的todos状态,让状态更新更可靠。

如果你觉得直接用document.querySelector不够“React风格”,也可以给每个<li>单独加ref,用数组存储这些ref,然后在useEffect里取最后一个ref来聚焦——不过对于这个场景,直接DOM查询已经足够简洁高效了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 07:21:09