【问题标题】:Checkbox inside map not getting checked地图内的复选框没有被选中
【发布时间】:2021-06-15 09:54:17
【问题描述】:

我是 react 新手,我正在尝试使用 react 钩子创建一个待办事项应用程序。我有一个数组 todoList,我在无序列表中显示它,每个 todo 元素都有一个复选框。现在,问题是复选框状态在单击时没有改变。我错过了什么?在 OnChange 中,我尝试直接更改 item.isDone 属性,并且我也尝试使用 setTodoList,但在这两种情况下,UI 中都没有发生任何事情。 useForm 只是另一个使用 useState 的模块。

const App = () => {
  const [todoVal, handleChange] = useForm({
    todoValue: "",
  });
  const [todoList, setTodoList] = useState([]);
  return (
    <div>
      <div className="container">
        <h1>Todo Item {todoVal.todoValue}</h1>
        <div className="row">
          <input
            type="text"
            placeholder="Write a todo"
            className="form-control"
            required
            name="todoValue"
            value={todoVal.todoValue}
            onChange={handleChange}
          />
          <button
            className="btn btn-primary btn-lg"
            onClick={() => {
              setTodoList([
                ...todoList,
                {
                  id: Math.floor(Math.random() * 20),
                  isDone: false,
                  value: todoVal.todoValue,
                },
              ]);
            }}
            disabled={!todoVal.todoValue.length}
          >
            Add Todo
          </button>
        </div>
        <div className="row">
          <ul className="list-group">
            {todoList.map((item, idx) => {
              return (
                <li key={item.id} className="list-group-item">
                  <div className="row">
                    <div className="col-xs-8 px-2">
                      <input
                        id={"isDone-" + item.id}
                        type="checkbox"
                        name={"isDone-" + item.id}
                        checked={item.isDone}
                        onChange={(e) => {
                             item.isDone = !item.isDone;
                        }}
                      />
                      <label for={"isDone-" + item.id}>{item.value} - {String(item.isDone)}</label>
                    </div>
                    <button
                      className="btn btn-danger"
                      onClick={() => {
                        const list = todoList.filter(
                          (todoItem) => todoItem.id !== item.id
                        );
                        setTodoList(list);
                      }}
                    >
                      Delete
                    </button>
                  </div>
                </li>
              );
            })}
          </ul>
        </div>
      </div>
    </div>
  );
};

export default App;

【问题讨论】:

  • 您必须更新单选按钮的 todoList onChange。您可以使用 id 跟踪记录。

标签: reactjs react-hooks


【解决方案1】:

只需将输入复选框中的 onChange 更改为

onChange ={ (e) => {
  // item.isDone = !item.isDone; you cannot change item directly as it's immutable
  setTodoList(
    todoList.map((it) =>
      it.id !== item.id ? it : { ...item, isDone: !item.isDone }
    )
  );
}}

说明:item对象是不可变的,所以是todoList。因此,当您想将其属性 isDone 设置为 true 时,您必须创建一个代理副本,然后使用 setTodoList。

所以我们调用 setTodoList 并使用 map 生成 todoList 的副本。如果 id 与您正在检查的不同,我们将保留 todoItem(它在我的代码中)原样,否则我们使用扩展运算符 {...} 创建它的副本并将其 isDone 属性更新为 true。

我们也可以使用immer 来生成一个代理可变对象,我们可以直接编辑它,但我认为在这种简单的情况下是多余的。

为了: 用npm i immer安装immer

在顶部:import {produce} from 'immer'; 那么:

onChange ={ (e) => {
 setTodoList(
    todoList.map((it) =>
      it.id !== item.id ? it : produce(item, (draftItem) => {
        draftItem.isDone = !draftItem.isDone;
      })
    )
  );
}}

【讨论】:

  • 但是有没有更好的方法来处理这个问题?什么是数组大小很大?
  • 我认为这不能以其他方式完成。我的 chrome 电脑在 10 秒内加载了 10000 个动态生成的待办事项,我认为前端应用程序的单个页面中不会有这么多元素。此外,当我单击复选框时,需要 2 秒才能完成执行并重新渲染。
【解决方案2】:

您已经有一个索引,因此您不需要遍历列表。您可以创建数组副本并更新特定索引:

onChande={ () => {
  setTodoList((prevTodoList) => {
    // create a copy of todoList
    const updatedTodoList = [...prevTodoList];

    // toggle isDone state at specific index
    updatedTodoList[idx].isDone = !updatedTodoList[idx].isDone;

    return updatedTodoList;
  });
}}

同样的事情可以应用于你的删除方法,所以你不需要过滤整个列表:

onClick={ () => {
  setTodoList((prevTodoList) => {
    // create a copy of todoList, needed because splice mutates array
    const updatedTodoList = [...prevTodoList];

    // remove item at specific index, mutates array
    updatedTodoList.splice(idx, 1);

    return updatedTodoList;
  });
}}

【讨论】:

  • 你说得对,我在 1000 万个对象列表上测试了这个。运行 map 需要 230 毫秒,而您的代码需要 50 毫秒。但是,我们谈论的是 1000 万个项目列表,但是是的,您的代码更有效率。
  • 是的,1000 万个项目列表是极不可能的,这段代码中的性能不应该是一个因素。在我看来,其中一个因素是简单易读的代码,利用已经可用的东西。简单性通常会产生易于理解和可读的代码,这在许多情况下也更有效。
猜你喜欢
  • 2014-08-18
  • 2021-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-29
相关资源
最近更新 更多