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

React中使用Array.filter()处理Todo列表时如何保留对象数组索引?

待办事项列表索引异常问题解决

问题场景

我做了一个简易待办事项列表,预期未完成项留在左侧容器,已完成项移至右侧容器。但遇到一个问题:使用Array.filter()按完成状态将项分类到新数组时,无法保留原索引,导致勾选的项虽能正确移至右侧,但左侧新数组中相同索引的项会被错误勾选。点击复选框时已重新获取查询更新状态,但问题依旧,刷新页面能解决,但希望用React的方式通过状态更新或保留索引来解决。

相关代码片段

GraphQL查询

const { loading, error, data } = useQuery(TODOS_QUERY);

数据结构

data.todos = [ 
  {
    "id":"1",
    "title":"1",
    "completed":true
 },
 {
    "id":"2",
    "title":"2",
    "completed":false
 },
 {
    "id":"3",
    "title":"3",
    "completed":true
 }
]

切换完成状态函数

const toggleCompleted = (id) => {
  toggle({variables: { id }});
  reset();
}

左侧容器(未完成项)代码

{data.todos
  .filter(item => item.completed === false)
  .map((todo, key) => (
    <li onChange={() => toggleCompleted(todo.id)}
    key={key}><FormControlLabel control={<Checkbox />} label={todo.title} /></li>
))}

右侧容器(已完成项)代码

{data.todos
    .filter((item) => item.completed === true)
    .map((todo, key) => (
      <li className="completed-item" onChange={() => toggleCompleted(todo.id)}
      key={key}><FormControlLabel control={<Checkbox defaultChecked />} label={todo.title} /></li>
))}

问题原因

核心问题在于使用数组索引作为React组件的key属性。当原数组经过filter生成新数组后,新数组的索引与原数组不再一一对应。React的Diff算法依赖key来识别组件身份,相同索引的组件会被复用,导致组件状态混乱。比如原数组中索引1的未完成项被移到右侧后,左侧新数组的索引0项会复用原索引1组件的勾选状态,从而出现错误勾选。

解决方案

1. 使用唯一ID作为组件key

每个待办项都有唯一的id字段,直接用todo.id替代索引作为key,让React能准确识别每个组件,无论数组如何过滤排序,都不会复用错误的组件状态。

修改后的左侧容器代码:

{data.todos
  .filter(item => item.completed === false)
  .map((todo) => (
    <li onChange={() => toggleCompleted(todo.id)}
    key={todo.id}><FormControlLabel control={<Checkbox />} label={todo.title} /></li>
))}

修改后的右侧容器代码:

{data.todos
    .filter((item) => item.completed === true)
    .map((todo) => (
      <li className="completed-item" onChange={() => toggleCompleted(todo.id)}
      key={todo.id}><FormControlLabel control={<Checkbox defaultChecked />} label={todo.title} /></li>
))}

2. 优化状态更新逻辑(可选)

当前toggleCompleted中调用的reset()会强制重置查询状态,可能导致不必要的重新渲染。如果使用Apollo Client,可以通过mutation的update函数手动更新缓存,让状态更新更及时,无需调用reset():

const [toggle] = useMutation(TOGGLE_TODO, {
  update(cache, { data: { toggleTodo } }) {
    // 读取当前缓存中的todos数据
    const { todos } = cache.readQuery({ query: TODOS_QUERY });
    // 更新对应todo的状态并写回缓存
    cache.writeQuery({
      query: TODOS_QUERY,
      data: { todos: todos.map(t => t.id === toggleTodo.id ? toggleTodo : t) }
    });
  }
});

const toggleCompleted = (id) => {
  toggle({ variables: { id } });
  // 移除reset()调用
}

这种方式会直接更新本地缓存,组件会立即根据最新数据重新渲染,性能更优。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 02:05:18