Next.js map渲染组件时所有组件storyId变量取值相同问题
Next.js map遍历渲染组件时事件回调获取变量值全一致问题
问题现象
- 使用
map方法遍历stories数组渲染Story子组件,每个子组件接收对应唯一的story业务数据 - 每个
Story组件内部定义了依赖当前项storyId的deleteStory异步删除方法,绑定到组件内的删除按钮点击事件 - 实际运行时点击任意删除按钮,
deleteStory方法内获取到的storyId值完全相同,没有和当前组件对应的故事项绑定,不符合预期
复现代码
父组件代码
export default function stories() { const { data: stories, mutate: mutateStories } = findUserStoriesWithThemes(); return ( <Container> <BootInnit/> {/* 其他业务代码 */} {stories?.story.map((story) => ( <div key = {story.id}> <Story story = {story} storyUrl = {returnUrlStory} mutateStories = {mutateStories}/> <hr/> </div> ))} </Container> ); }
Story子组件代码
export default function Story({story, storyUrl, mutateStories}// 传入mutateStories用于刷新列表 { const storyId = story.id; console.log(storyId); const deleteStory = async() => { console.log(storyId); // 数据库删除操作逻辑 } return( <div> {/*其他业务代码*/} <button className = "btn btn-danger" onClick = {deleteStory} type = "button" data-bs-dismiss = "modal">Delete</button> </div> ); }
问题根因
问题出在删除按钮上的data-bs-dismiss="modal"属性,这是Bootstrap模态框的原生属性:
- 现有实现是把所有故事项的删除确认逻辑都绑定到了同一个公共模态框上,不是每个故事项对应独立的模态框
- Bootstrap初始化模态框事件时,只会绑定最后一次渲染挂载到确认按钮上的回调,这个回调的闭包捕获的永远是最后一个渲染项的
storyId,所以不管点哪个删除按钮,拿到的id都是同一个 - 如果组件顶层打印的
storyId正常、只有点击触发时id不对,就能确认是这个问题,和React的map遍历、闭包陷阱没有关系
修复方案
两种方案选其一即可:
- 方案1:每个Story项渲染独立模态框
不复用全局公共删除确认模态框,给每个Story项单独挂载属于自己的模态框,绑定对应项的deleteStory方法,保证点击确认时触发的是当前项的回调,闭包捕获的storyId自然正确。 - 方案2:全局模态框通过状态存储当前待删除的storyId
保留单个全局删除确认模态框,新增状态存储当前待删除的storyId:- 点击列表项的删除按钮时,先把当前项的
storyId存入状态,再唤起模态框 - 模态框的确认删除按钮直接读取状态里的
storyId执行删除逻辑即可,不需要从列表项组件的闭包里取值
- 点击列表项的删除按钮时,先把当前项的
方案2核心参考代码:
// 父组件内管理删除相关状态 const [deleteTargetId, setDeleteTargetId] = useState(null) const [showDeleteModal, setShowDeleteModal] = useState(false) const handleConfirmDelete = async () => { if (!deleteTargetId) return // 调用删除接口,用deleteTargetId作为要删除的故事id await deleteStoryRequest(deleteTargetId) mutateStories() // 刷新列表 setShowDeleteModal(false) setDeleteTargetId(null) } // 遍历渲染时给每个Story传入唤起删除模态框的方法 {stories?.story.map((story) => ( <div key={story.id}> <Story story={story} storyUrl={returnUrlStory} mutateStories={mutateStories} onTriggerDelete={() => { setDeleteTargetId(story.id) setShowDeleteModal(true) }} /> <hr/> </div> ))} // 全局模态框的确认按钮直接绑定handleConfirmDelete即可
内容的提问来源于stack exchange,提问作者Zorark
相关产品推荐
相关产品推荐

