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
相关产品推荐
相关产品推荐

