React Material UI跨组件传递保存点击函数的优化咨询
现有实现的问题
你当前通过共享空对象、子组件往对象上挂载方法的写法属于React反模式,存在几个明显隐患:
- 父组件每次渲染都会重新创建空的
callback对象,子组件的useEffect如果没有正确配置依赖项,很容易出现方法丢失、闭包捕获旧状态/旧属性的问题 - 违背React单向数据流设计原则,子组件随意修改父组件传入的对象,后续调试时很难追踪方法的定义来源
- 没有容错机制,一旦子组件没有成功挂载
saveCard方法,父组件点击按钮会直接抛出运行时错误
最优实现方案(推荐)
优先选择这个方案,把提交按钮收拢到CardPaymentForm组件内部,表单校验、提交触发的逻辑本来就属于表单组件的内聚职责,父组件只需要传入提交成功后的业务回调即可,完全符合React单向数据流的设计要求,维护成本最低。
父组件改造代码
// 删除原有 const callback = {} 定义 return ( <> {!state?.creditCard?.isSaved ? ( <Paper elevation={4} className={classes.paymentContainer}> <Box className={classes.subPaymentContainer}> <Typography className={classes.title}>Card Payment</Typography> <CardPaymentForm // 仅传入表单校验通过后要执行的业务逻辑 onSubmitSuccess={() => actionsCollection.booking.saveCard(true, state.creditCard.lastFourDigits)} formType="profileForm" /> <div style={{ display: "flex", marginTop: 20 }}> <img className={classes.lockIcon} src={lockIconInfo} alt="" /> <Typography className={classes.paymentInfo}> <Link href="/terms" target={"_blank"}> Terms of Payment </Link> . </Typography> </div> </Box> </Paper> ) : ( <div style={{ height: 373 }}> <CardStored removeCard={removeCard} /> </div> )} </> );
子组件改造代码
const CardPaymentForm = ({ classes, onSubmitSuccess, formType, lastFourDigits }) => { // 保留原有表单校验逻辑 const validateForm = () => { // 自定义表单校验规则 }; const handleSave = (e) => { e.preventDefault(); if (validateForm()) { onSubmitSuccess(); } }; return ( <> {/* 原有表单输入项渲染逻辑 */} <div> <Button type="submit" onClick={handleSave} value="SAVE CREDIT CARD" className={classes.button} /> </div> </> ) }
备选方案(布局限制时使用)
如果因为页面布局要求,提交按钮必须放在父组件层级、无法收拢到子组件内部,使用React官方提供的forwardRef+useImperativeHandle能力实现子组件方法暴露,替代你现在手动往共享对象上挂方法的写法。
子组件改造代码
import { forwardRef, useImperativeHandle } from 'react'; const CardPaymentForm = forwardRef(({ classes, onSubmitSuccess, formType, lastFourDigits }, ref) => { const validateForm = () => { // 原有表单校验逻辑 }; // 显式暴露给父组件的方法,正确配置依赖项就不会出现闭包问题 useImperativeHandle(ref, () => ({ saveCard: (e) => { e.preventDefault(); if (validateForm()) { onSubmitSuccess(); } } }), [onSubmitSuccess]); return ( // 原有表单输入项渲染逻辑 ) });
父组件改造代码
import { useRef } from 'react'; // 父组件内创建ref绑定子组件实例 const cardFormRef = useRef(null); return ( <> {!state?.creditCard?.isSaved ? ( <Paper elevation={4} className={classes.paymentContainer}> <Box className={classes.subPaymentContainer}> <Typography className={classes.title}>Card Payment</Typography> <CardPaymentForm ref={cardFormRef} onSubmitSuccess={() => actionsCollection.booking.saveCard(true, state.creditCard.lastFourDigits)} formType="profileForm" /> <div> <Button type="submit" onClick={(e) => cardFormRef.current?.saveCard(e)} value="SAVE CREDIT CARD" className={classes.button} /> <div style={{ display: "flex", marginTop: 20 }}> <img className={classes.lockIcon} src={lockIconInfo} alt="" /> <Typography className={classes.paymentInfo}> <Link href="/terms" target={"_blank"}> Terms of Payment </Link> . </Typography> </div> </div> </Box> </Paper> ) : ( <div style={{ height: 373 }}> <CardStored removeCard={removeCard} /> </div> )} </> );
注意:调用子组件暴露的方法时加了可选链
?.,即使子组件还没挂载完成,点击按钮也不会抛错,容错性更好。
内容的提问来源于stack exchange,提问作者Sudhir
相关产品推荐
相关产品推荐

