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

React中使用useState处理动态文本输入框问题求助

解决方案

问题根源

你当前所有编辑输入框共用同一个editTodo状态,导致输入时所有框同步更新,同时状态变更触发的重渲染会让输入框失去焦点。要解决这个问题,核心是让每个待办项拥有独立的编辑状态,完全用useState实现有两种可行思路:


思路1:让Todo组件独立管理自身编辑状态

把Todo改成带状态的组件,每个Todo自己维护编辑输入框的值,这样各个待办项的编辑状态完全隔离,不会互相影响。

修改后的完整代码:

import "./theme.css"
import * as appStyles from "./styles/App.module.css"
import * as todoStyles from "./styles/Todo.module.css"
import { useState } from "react"

const initialState = [
  {
    id: "1",
    name: "My first ToDo",
    status: "new",
  },
]

export function App() {
  const [numofItems, setNumofItems] = useState(2)
  const [newToDo, setnewToDo] = useState('');
  const [todos, setTodos] = useState(initialState);

  const onAddTodo = () => {
    if (!newToDo.trim()) return; // 过滤空内容
    setnewToDo("");
    setTodos((old) => [
      ...old,
      { id: numofItems.toString(), name: newToDo, status: "new" },
    ])
    setNumofItems(numofItems + 1);
  }

  const deleteList = () =>{
    setTodos([]);
  }

  const updateTodoName = (id, newName) => {
    setTodos(prev => prev.map(item => 
      item.id === id ? {...item, name: newName.trim()} : item
    ));
  }

  const handleMove = (id, position) =>{
    const todoIndex = todos.findIndex(obj => obj.id === id);
    if(position === "up"){
      if (todoIndex <= 0) return;
      const newTodos = [...todos];
      [newTodos[todoIndex - 1], newTodos[todoIndex]] = [newTodos[todoIndex], newTodos[todoIndex - 1]];
      setTodos(newTodos);
    }
    else if(position === "down"){
      if (todoIndex >= todos.length - 1) return;
      const newTodos = [...todos];
      [newTodos[todoIndex + 1], newTodos[todoIndex]] = [newTodos[todoIndex], newTodos[todoIndex + 1]];
      setTodos(newTodos);
    }
  }

  const deleteListItem = (todoid) => {
    setTodos(todos.filter(({id}) => id !== todoid))
  }

  // 独立带状态的Todo组件
  const Todo = ({ record, onUpdate, onDelete, onMove }) => {
    const [editValue, setEditValue] = useState(record.name);

    const handleSave = () => {
      onUpdate(record.id, editValue);
    }

    return (
      <li className={todoStyles.item}>
        {record.name}
        <button className={appStyles.editButtons} onClick={() => onDelete(record.id)}>Delete</button>
        <button className={appStyles.editButtons} onClick={handleSave}>Edit</button>
        <button className={appStyles.editButtons} onClick={() => onMove(record.id, "down")}>Move down</button>
        <button className={appStyles.editButtons} onClick={() => onMove(record.id, "up")}>Move up</button>
        <input 
          className={appStyles.input}
          type="text"
          value={editValue}
          onChange={event => setEditValue(event.target.value)}
        />
      </li>
    )
  }

  return (
    <>
      <h3 className={appStyles.title}>React ToDo App</h3>
      <ul className={appStyles.list}>
        {todos.map((t) => (
          <Todo 
            key={t.id} // 用todo的id作为key,避免顺序变化导致的重渲染问题
            record={t} 
            onUpdate={updateTodoName}
            onDelete={deleteListItem}
            onMove={handleMove}
          />
        ))}
      </ul>
      <div className={appStyles.actions}>
        <form onSubmit={(e) => {e.preventDefault(); onAddTodo();}}>
          <label>
            Enter new item:
            <input 
              className={appStyles.input} 
              type="text" 
              name="newToDo" 
              value={newToDo} 
              onChange={event => setnewToDo(event.target.value)}
            />
          </label>
          <button className={appStyles.button} type="submit">Add</button>
        </form>
        <br></br>
        <button className={appStyles.button} onClick={deleteList}>
          Delete List
        </button>
      </div>
    </>
  )
}

关键修改点:

  • 将Todo改为独立维护editValue状态的组件,每个待办项的输入值完全隔离
  • 父组件只负责全局的todo列表状态,通过回调函数接收子组件的更新请求
  • 修复了原代码中deleteList的this调用错误(函数式组件不能用this)
  • 用数组解构简化了上下移动的逻辑,更简洁易读
  • 将列表项的key改为todo的id,避免因列表顺序变化导致的不必要重渲染
  • 添加了表单提交的默认行为阻止,以及空内容过滤

思路2:父组件维护按ID映射的编辑状态

如果希望编辑状态统一由父组件管理,可以把editTodo改成一个对象,键是todo的id,值是对应待办项的编辑输入值,这样每个输入框绑定对应的状态属性:

修改后的核心代码片段:

export function App() {
  // 其他状态不变,将editTodo改为按id映射的对象
  const [editTodo, setEditTodo] = useState({});

  // 处理编辑输入的变化
  const handleEditInputChange = (id, value) => {
    setEditTodo(prev => ({...prev, [id]: value}));
  }

  const handleEdit = (id) => {
    const newName = editTodo[id] || '';
    if (!newName.trim()) return;
    setTodos(prev => prev.map(item => 
      item.id === id ? {...item, name: newName.trim()} : item
    ));
    // 清空该id的编辑状态
    setEditTodo(prev => {
      const newState = {...prev};
      delete newState[id];
      return newState;
    });
  }

  const Todo = ({ record }) => {
    return (
      <li className={todoStyles.item}>
        {record.name}
        <button className={appStyles.editButtons} onClick={() => deleteListItem(record.id)}>Delete</button>
        <button className={appStyles.editButtons} onClick={() => handleEdit(record.id)}>Edit</button>
        <button className={appStyles.editButtons} onClick={() => handleMove(record.id, "down")}>Move down</button>
        <button className={appStyles.editButtons} onClick={() => handleMove(record.id, "up")}>Move up</button>
        <input 
          className={appStyles.input}
          type="text"
          value={editTodo[record.id] || record.name} // 默认显示原名称
          onChange={event => handleEditInputChange(record.id, event.target.value)}
        />
      </li>
    )
  }

  // 其余代码保持不变(注意修复deleteList的this问题等)
}

关键修改点:

  • editTodo从单个对象变为{ [id]: 输入值 }的映射结构,每个待办项对应独立的状态属性
  • 输入框的value绑定editTodo[record.id],没有时默认显示原todo名称
  • 编辑完成后删除对应id的状态属性,保持状态干净

两种方案都完全基于useState实现,没有使用refs,且解决了输入框共用状态导致的失焦、同步更新问题。推荐第一种思路,组件职责更清晰,状态隔离更彻底。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 11:06:24