React可折叠容器如何向未知子组件传递内部props?
可折叠表单容器状态透传实现方案
需求背景
开发可复用的CollapsableContainer可折叠容器组件,用于表单分区展示,支持两种状态切换:
- 展开状态:展示分区内的表单编辑字段
- 折叠状态:展示分区标题、修改按钮、分区内容摘要
组件初始实现代码:
function CollapsableContainer({ title, children }: CollapsableContainerProps) { const [isOpen, setIsOpen] = useState(false) return ( <div className="comp-collapsable-container"> {isOpen ? ( {children} ) : ( <> <div className="header-section"> <Typography variant="h6"> {title} </Typography> <Button onClick={() => setIsOpen(true)} startIcon={<EditIcon />}> Modify </Button> </div> <div className="summary-section"> /* Summary here */ </div> </> )} </div> ) }
核心目标:将容器内部的折叠控制方法传递给子表单组件,实现用户点击表单提交按钮时自动触发容器折叠,无需在每个使用场景重复编写折叠逻辑。
典型传入子组件为Formik表单结构,需要将折叠逻辑注入表单onSubmit流程,子组件示例:
<Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={onSubmit} > <Form> <FormikTextField id="name" name="name" label="name" fullWidth required /> <FormikTextField id="description" name="description" label="description" fullWidth required /> </Form> </Formik>
可行实现方案
方案1:React.cloneElement注入props(推荐,零额外调用成本)
不需要修改外部调用子组件的写法,直接在容器内部克隆子元素,把封装好的关闭方法注入子组件props即可。
容器组件修改后代码:
import { cloneElement, useState, ReactElement } from 'react'; function CollapsableContainer({ title, children }: CollapsableContainerProps) { const [isOpen, setIsOpen] = useState(false) // 单独封装关闭方法,不要直接暴露setIsOpen,避免外部误操作状态 const closeContainer = () => setIsOpen(false) return ( <div className="comp-collapsable-container"> {isOpen ? ( // 克隆直接子元素,注入关闭方法 cloneElement(children as ReactElement, { closeContainer }) ) : ( <> <div className="header-section"> <Typography variant="h6"> {title} </Typography> <Button onClick={() => setIsOpen(true)} startIcon={<EditIcon />}> Modify </Button> </div> <div className="summary-section"> /* Summary here */ </div> </> )} </div> ) }
子组件使用时只需要在props中接收closeContainer,在提交逻辑完成后调用即可,Formik表单适配示例:
// 表单组件接收容器注入的closeContainer const SectionForm = ({ initialValues, validationSchema, onSubmit, closeContainer }) => { const handleSubmit = async (values, formikHelpers) => { // 执行原有业务提交逻辑 await onSubmit(values, formikHelpers) // 提交完成后折叠容器 closeContainer() } return ( <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit} > <Form> <FormikTextField id="name" name="name" label="name" fullWidth required /> <FormikTextField id="description" name="description" label="description" fullWidth required /> <Button type="submit">提交</Button> {/* 可以额外加取消按钮,点击也调用closeContainer */} <Button onClick={closeContainer}>取消</Button> </Form> </Formik> ) } // 业务侧使用时不需要额外传closeContainer,容器会自动注入 <CollapsableContainer title="基础信息"> <SectionForm initialValues={baseInfoInitialValues} validationSchema={baseInfoSchema} onSubmit={submitBaseInfo} /> </CollapsableContainer>
适配提示:如果容器下有多个直接子元素,可以遍历
Children.map(children, child => cloneElement(child, { closeContainer }))逐个注入。
方案2:Render Props模式(灵活度更高,适合复杂场景)
将children改为渲染函数类型,容器把关闭方法作为参数传给渲染函数,业务侧可以灵活控制调用时机。
容器组件类型和实现修改:
// 更新props类型定义 type CollapsableContainerProps = { title: string; children: (handlers: { closeContainer: () => void }) => React.ReactNode; } function CollapsableContainer({ title, children }: CollapsableContainerProps) { const [isOpen, setIsOpen] = useState(false) const closeContainer = () => setIsOpen(false) return ( <div className="comp-collapsable-container"> {isOpen ? ( // 执行渲染函数,传入控制方法 children({ closeContainer }) ) : ( <> <div className="header-section"> <Typography variant="h6">{title}</Typography> <Button onClick={() => setIsOpen(true)} startIcon={<EditIcon />}> Modify </Button> </div> <div className="summary-section">/* Summary here */</div> </> )} </div> ) }
业务侧使用方式:
<CollapsableContainer title="基础信息"> {({ closeContainer }) => ( <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={async (values, helpers) => { await onSubmit(values, helpers) closeContainer() }} > <Form> <FormikTextField id="name" name="name" label="name" fullWidth required /> <FormikTextField id="description" name="description" label="description" fullWidth required /> <Button type="submit">提交</Button> <Button onClick={closeContainer}>取消</Button> </Form> </Formik> )} </CollapsableContainer>
优化建议
- 可以扩展摘要区实现:通过注入的方法拿到表单提交值,自动生成摘要内容,进一步减少重复代码
- 如果后续其他组件也需要折叠控制逻辑,可以把折叠状态和方法抽成自定义Hook
useCollapsable复用 - 可以增加动画过渡效果,优化折叠/展开的交互体验
内容的提问来源于stack exchange,提问作者Antoine Lessard
相关产品推荐
相关产品推荐

