关于高阶组件的实践
背景
在我刚接触react的时候,就有看到面经频繁提到的高阶组件(High Order Component)理念,说它是一个很重要的理念,可是却只提供了比较简单的场景,例如实现一个权限 校验的高阶组件。
后面在本人自己的工作实践过程中,打磨出一种自认为比较好用的实践,是关于一个对抽屉/弹窗的hoc封装。
为什么需要这种封装?
抽屉/弹窗类组件有一些共同的逻辑:
- 要被打开/关闭
- 通常需要在父组件中声明多个状态(
XXXVisible
, 传入的数据等) - 如果有多个串行的抽屉/弹窗表单,代码可能变得难以阅读和维护
我认为用更少的state,更多的compute来实现功能,才是遵循React官方推崇的声明式地考虑 UI 。
实现
所以我写了关于弹窗/抽屉的高阶组件。
使用了arco-design-react组件
live demo
实时编辑器
function MyPlayground() { function DrawerHOC(functionComponent) { return forwardRef((props, ref) => { const [open, setOpen] = useState(false); const [data, setData] = useState({}); const close = () => { setOpen(false); props?.onClose?.(); }; useImperativeHandle(ref, () => { return { open(data = null) { setOpen(true); if (data) setData(data); }, close, }; }); let children; let drawerProps; const result = functionComponent({ close, data, open, outerProps: props }); if (Array.isArray(result)) { children = result[0]; drawerProps = result[1]; } else { children = result; drawerProps = {}; } return ( <Modal onCancel={close} visible={open} unmountOnExit={true} {...props} {...drawerProps} > {children} {props.children} </Modal> ); }); } const DrawerPersonalInfoEdit = DrawerHOC(function ({close, open, data, outerProps}) { const [form] = Form.useForm() const {userId, onOk = ()=> {}} = data useEffect(()=>{ if (!open) return setTimeout(() => { // 使用userId获取用户信息,再设置表单? form.setFieldsValue({ name: "ybr", age: 29 }) }, 500); }, [open]) return [ <div> <Form form={form} layout="vertical"> <Form.Item label="姓名" field="name"> <Input /> </Form.Item> <Form.Item label="年龄" field="age"> <InputNumber /> </Form.Item> </Form> </div>, { title: "个人信息", width: 500, onOk: () => { form.validate().then(values => { onOk?.(close, values) }) }, onCancel: () => { close() form.resetFields() } } ] }) const DrawerDeptInfoEdit = DrawerHOC(function ({close, open, data, outerProps}) { const [form] = Form.useForm() const {values, onOk = ()=> {}} = data return [ <div> <h6>个人信息</h6> {JSON.stringify(values)} <Form form={form} layout="vertical"> <Form.Item label="部门" field="deptName"> <Input /> </Form.Item> </Form> </div>, { title: "部门信息", width: 500, onOk: () => { form.validate().then(values => { onOk?.(close, values) }) }, onCancel: () => { close() form.resetFields() } } ] }) const drawerPersonalInfoEditRef = useRef() const drawerDeptInfoEditRef = useRef() return <div> <DrawerPersonalInfoEdit ref={drawerPersonalInfoEditRef} /> <DrawerDeptInfoEdit ref={drawerDeptInfoEditRef} /> <Button type="primary" onClick={()=>{ drawerPersonalInfoEditRef.current.open({ userId: "1", onOk: (drawerPersonalInfoEditClose, values) => { drawerPersonalInfoEditClose(); drawerDeptInfoEditRef.current.open({ values, onOk(drawerDeptInfoEditClose, values2) { if (window.confirm("是否关闭")) { alert(JSON.stringify({...values, ...values2})) drawerDeptInfoEditClose(); } } }) } }) }}> 编辑 </Button> </div> }
结果
Loading...
这么使用,有以下好处:
- 封装了抽屉/弹窗的通用逻辑,如 open/close 方法,使业务组件可以更专注于实现具体功能。
- 业务组件与抽屉/弹窗状态紧密结合,既可以修改状态,也能响应状态变化。例如
DrawerPersonalInfoEdit
组件只在抽屉打开时获取用户信息。 - 简化了串行交互的实现。可以通过回调函数优雅地处理多个抽屉/弹窗之间的数据传递和流程控制。
- 减少了在父组件中声明的状态数量,符合 React 官方推荐的"声明式 UI"思想。
- 提高了代码的可读性和可维护性,特别是在处理复杂的表单流程时。
- 通过 HOC 模式实现了逻辑复用,使得创建新的抽屉/弹窗组件变得更加简单和统一。
总结
这种实践充分利用了 HOC 的优势,在保持灵活性的同时,大大简化了抽屉/弹窗相关的状态管理和交互逻辑。