React中如何在多个父组件中复用同一个CloseButton子组件
现有代码问题
你当前写的CloseButton没法在两个父组件里正常复用,核心原因有三点:
- 组件内部直接通过
document.querySelector('.container_blur')查找DOM节点操作显隐,两个父组件的根节点都用了container_blur类名,两个组件同时渲染时会选错操作目标,且直接操作DOM不符合React状态驱动的开发逻辑 - CloseButton的
onClick属性为空,内部定义的close函数根本没有绑定到点击事件上,点击无响应 - 两个父组件都没有维护自身的显隐状态,关闭逻辑完全耦合在子组件的DOM操作里,拆分复用会互相干扰
改造方案
核心思路是把CloseButton改成无状态的纯UI组件,关闭逻辑交给父组件自行控制,同一个组件可以在任意父组件中传入对应回调即可复用,不会产生冲突。
1. 重构CloseButton组件
移除内部的DOM操作逻辑,只保留UI渲染和事件触发能力,关闭方法通过props从父组件接收:
// Close_Button.js export default function Close_Button({ onClose }) { return ( <div onClick={onClose} className='d-flex close_button'> <svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12.5" cy="12.5" r="12" stroke="#896DF6" /> <path d="M17 8L8 17M17 17L8 8" stroke="#896DF6" stroke-width="3" stroke-linecap="round" /> </svg> </div> ) }
原有的淡出过渡效果不用setTimeout写在内联样式里,直接通过CSS类切换实现,维护成本更低:
/* 全局样式中添加 */ .container_blur { opacity: 1; transition: opacity 0.3s ease-out; } .container_blur.fade-out { opacity: 0; pointer-events: none; }
2. 改造父组件
每个父组件单独维护自身的显隐状态,实现自己的关闭逻辑,把关闭方法传给内部的CloseButton即可,两个父组件可以完全复用同一个CloseButton组件:
// ParentOne.js import { useState } from 'react'; import Close_Button from './Close_Button'; // 替换为你的组件实际路径 import style from './style.module.css'; // 替换为你的样式文件实际路径 import Image from 'next/image'; // 若使用普通img标签可移除该引入 import Input_search from './Input_search'; // 替换为你的组件实际路径 export default function ParentOne() { const [isVisible, setIsVisible] = useState(true); const [list, setList] = useState([ { id: 1, name:'Bitcoin', image:'/bitcoin-logo.svg' }, { id: 2, name:'Etherium', image:'/eth-logo.svg' }, { id: 3, name:'Dogecoin', image:'/doge-logo.svg' }, { id: 4, name:'Bitcoin', image:'/bitcoin-logo.svg' }, { id: 5, name:'Etherium', image:'/eth-logo.svg' }, { id: 6, name:'Dogecoin', image:'/doge-logo.svg' }, { id: 7, name:'Bitcoin', image:'/bitcoin-logo.svg' }, { id: 8, name:'Etherium', image:'/eth-logo.svg' }, { id: 9, name:'Dogecoin', image:'/doge-logo.svg' } ]) const handleClose = () => { const container = document.getElementById('select_token'); container.classList.add('fade-out'); setTimeout(() => { setIsVisible(false); container.classList.remove('fade-out'); }, 300) } if (!isVisible) return null; return ( <section className='container_blur' id='select_token' > <div className={style.token_container}> <div className={style.nav}> <span>Select token</span> <Close_Button onClose={handleClose} /> </div> <Input_search /> <div className={style.coin_container}> <ul className={style.ul}> <div className={style.li}> {list.map((item) => <li key={item.id}> <Image src={item.image} width={27} height={35} alt={item.name} /> <p>{item.name}</p> </li> )} </div> </ul> </div> </div> </section> ) }
第二个父组件改造逻辑完全一致:
// ParentTwo.js import { useState } from 'react'; import Close_Button from './Close_Button'; import style from './style.module.css'; import Image from 'next/image'; export default function ParentTwo() { const [isVisible, setIsVisible] = useState(true); const [token, setToken] = useState([ {id:1, name:'MetaMask', image:'/metaMask_Fox.png'}, {id:2, name:'WalletConnect', image:'/wallet-connect.png'}, {id:3, name:'KeyStone', image:'/keystone.svg'}, {id:4, name:'Torus', image:'/torus.svg'}, {id:5, name:'CoinBase Wallet', image:'/coinbase-wallet.png'}, {id:6, name:'Fortmatic', image:'/fortmatic.svg'} ]) const handleClose = () => { const container = document.getElementById('connect_w'); container.classList.add('fade-out'); setTimeout(() => { setIsVisible(false); container.classList.remove('fade-out'); }, 300) } if (!isVisible) return null; return ( <section className='container_blur' id='connect_w'> <div className={style.container_wallet}> <div className={style.nav}> <span>Connect wallet</span> <Close_Button onClose={handleClose} /> </div> <div className={style.wallet_wrap}> <div className='d-flex flex-wrap align-items-stretch justify-content-between'> { token.map((item, id) => <div key={id} className={style.wallet_select}> <p>{item.name}</p> <Image src={item.image} width={32} height={32} alt={item.name} /> </div> )} </div> </div> </div> </section> ) }
注意事项
- 原代码中
list.map的遍历参数和外层数组变量同名,容易引发变量污染,已统一改为item - ParentOne原列表数据存在id重复问题,会导致React渲染key异常,已修正为唯一id
- 如果两个父组件的关闭逻辑完全一致,可以进一步把关闭过渡逻辑抽为自定义hook,减少重复代码
内容的提问来源于stack exchange,提问作者David Julian
相关产品推荐
相关产品推荐

