React点击删除按钮无法正确移除指定组件问题咨询
问题描述
这是我的Add_Blog.js文件:
import React, { useState } from "react"; function AddBlogs() { const ADD = () => { return ( <div> <label>Topic</label> <input></input> <label>Content</label> <textarea></textarea> <button type="button" onClick={deleteContent} value={state.length}> Delete</button> </div> ); } const [state, setState] = useState([<ADD key="first"></ADD>]); const addblog = () => { setState([...state, <ADD key={state.length}></ADD>]) } const deleteContent = (event, key) => { setState([...state, state.splice(event.target.value, 1)]); } return ( <div> <form> <div> <label>Add Title</label> <input></input> </div> <div> { state } </div> <div> <button type="button" onClick={addblog}>Add another topic</button> </div> <input type="submit" value="publish"></input> </form> </div> ); } export default AddBlogs;
我对这段代码的预期效果是:点击删除按钮时,仅移除该按钮对应的单个输入组件。但实际运行时点击删除按钮,会移除目标组件以及其下方的所有组件,请问该问题应如何修复?
问题原因
代码里有两个核心bug,才会导致删除时多删组件:
- 删除逻辑完全写错了:
Array.splice是直接修改原数组的方法,返回值是被删掉的元素组成的数组,不是删除后剩下的数组。你写的setState([...state, state.splice(event.target.value, 1)])相当于先把原数组从对应位置开始删元素,再把原数组剩余元素和刚删出来的元素重新拼接,元素顺序、数量全是错的。 - 删除按钮拿的索引不对:你给按钮设置的
value={state.length}是渲染那一刻整个组件列表的总长度,根本不是当前输入块对应的索引。比如你加了3个输入块,所有删除按钮的value都是3,点任何一个都会从错误的位置开始删,而且新增组件的时候之前渲染好的按钮value不会自动更新,索引全是错位的。
另外你直接把JSX元素存在state里的写法也不推荐,后续要拿输入值、做校验都会很麻烦,很容易出现状态不同步的问题;把单条输入的组件定义在父组件内部,每次重渲染都会重新创建组件引用,也会触发不必要的重渲染甚至状态异常。
修复方法
最稳妥的改法是把state从存JSX改成存纯数据,用唯一id标识每一个输入块,从根源避免索引错位的问题,修复后的完整代码如下:
import React, { useState } from "react"; // 单条主题输入组件抽离到父组件外,避免重复创建组件引用,接收当前项id和删除回调作为参数 const BlogItem = ({ id, onDelete }) => { return ( <div> <label>Topic</label> <input /> <label>Content</label> <textarea /> {/* 点击时直接传入当前项的唯一id,不需要靠按钮value传索引 */} <button type="button" onClick={() => onDelete(id)}> Delete</button> </div> ); } function AddBlogs() { // 自增id,保证每一个新增的输入块都有唯一标识,不使用索引作为标识 const [nextId, setNextId] = useState(1); // state只存输入块的标识数据,后续要存输入内容直接扩展成对象即可 const [blogList, setBlogList] = useState([{ id: 0 }]); const addBlog = () => { setBlogList([...blogList, { id: nextId }]); setNextId(nextId + 1); } const deleteBlog = (targetId) => { // 过滤掉对应id的项,不修改原数组,逻辑简单不会出错 setBlogList(blogList.filter(item => item.id !== targetId)); } return ( <div> <form> <div> <label>Add Title</label> <input /> </div> <div> {blogList.map(item => ( <BlogItem key={item.id} id={item.id} onDelete={deleteBlog} /> ))} </div> <div> <button type="button" onClick={addBlog}>Add another topic</button> </div> <input type="submit" value="publish" /> </form> </div> ); } export default AddBlogs;
改完之后点击任意删除按钮,只会移除对应的那一个输入块,不会影响其他组件,后续要获取每个输入框的内容也只需要给state里的项加content、topic字段,绑定onChange即可。
如果你不想大改原有结构,只做最小修复的话,只需要改两处:
- 渲染ADD组件时传入当前索引,删除按钮直接用这个索引作为value,不要用
state.length - 把删除逻辑里的splice用法改掉,不要修改原数组,用
filter过滤对应索引即可。
内容的提问来源于stack exchange,提问作者Roshan Ojha
相关产品推荐
相关产品推荐

