问题描述:
forwardRef和readux的connect冲突,导致ref.current获取不到值
我想封装一个组件,父组件调用子组件的方法。但因为组件里使用了redux的connect导致ref一直获取不到值。
import React, { useState, useEffect, ReactNode, forwardRef, useImperativeHandle } from 'react';
import { Modal} from 'antd';
import { connect } from 'react-redux'
import * as actions from '@/store/actions'
import "./index.less";
interface Props {
/** 标题内容 */
title?: string | ReactNode;
/** 操作类型 */
type: "add" | "edit";
/** 数据 */
fundData?: any;
storeData?: any;
setStoreData?: any;
children?: ReactNode;
}
/**
* 策略弹窗表单
* 本组件可传一children点击传入的children来控制弹窗打开
* 还可以用ref 来控制弹窗打开和关闭
* 父组件引用useRef const modalRef:any = useRef(); modalRef.current?.openModal();
* 然后<ClModalForm type="edit" fundData={{}} ref={modalRef}/>
*
*/
const ClModalForm = forwardRef((props: Props, ref) => {
const { title, children } = props;
//编辑策略 Modal是否展示
const [cl, setCl] = useState(false);
// 暴露 openModal、handleCancel 方法给父组件
useImperativeHandle(ref, () => ({
openModal,
handleCancel,
}));
const handleCancel = () => {
setCl(false);
};
/** 打开弹窗 */
const openModal = () => {
setCl(true);
};
return (
<div>
{children && <div onClick={openModal}>{children}</div>}
<Modal title={title || "编辑策略"}
visible={cl}
onCancel={handleCancel}
width={560}
destroyOnClose
bodyStyle={{
maxHeight: "70vh", // 控制最大高度
overflowY: "auto", // 垂直滚动
}}
centered
>
</Modal>
</div>
)
});
export default connect((state) => state, actions)(ClModalForm);
使用:
const modalRef: any = useRef();
<ClModalForm title={"补充策略信息"} type="edit" fundData={currentRow} ref={modalRef} />
然后
if(modalRef.current){
modalRef.current?.openModal();
}
问题原因:
1.Redux connect 会阻断 ref 转发,默认不会向下传递 ref。
2.直接使用 connect(…)(forwardRef(…)) 时,ref 会被高阶组件拦截。
解决方案:
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
1. forwardRef配置 | React Redux 6+ | 最简洁官方方案 | 需要较新版本 |
2. 双forwardRef | 所有版本 | 通用性强 | 代码稍复杂 |
3. 自定义ref属性 | 简单场景 | 无需高阶组件 | 非标准ref用法 |
4. withRef | Redux 5.x | 旧版兼容方案 | 已弃用 |
方案一:最佳实践 - 使用 React Redux 的 forwardRef 配置(推荐):
导出把ref透传打开:
export default connect((state) => state, actions, null,
{ forwardRef: true } // 显式启用 ref 不加父组件拿不到子组件的ref
)(ClModalForm);
或者:
// 解决方案:添加 { forwardRef: true } 配置
const ConnectedClModalForm = connect(
(state) => state,
actions,
null,
{ forwardRef: true } // 关键配置
)(ClModalForm);
export default ConnectedClModalForm;
redux版本<6请看方案四!
版本兼容性注意事项:
v6.0 以下:必须用 { withRef: true },并通过 getWrappedInstance() 间接访问 ref1。
v6.0 及以上:优先使用 { forwardRef: true },直接获得 ref 引用。
方案二:双 forwardRef 包裹法:
两层转发:
const InnerClModalForm = forwardRef<ModalHandles, Props>((props, ref) => {
// ...组件内部逻辑不变
});
const Connected = connect((state) => state, actions)(InnerClModalForm);
// 外层 forwardRef 处理
const ClModalForm = forwardRef<ModalHandles, Props>((props, ref) => (
<Connected {...props} forwardedRef={ref} />
));
export default ClModalForm;
方案三:自定义 ref 属性传递:
interface Props {
innerRef?: React.Ref<ModalHandles>;
// ...其他props
}
const ClModalForm = (props: Props) => {
// 使用 props.innerRef 替代 ref
useImperativeHandle(props.innerRef, () => ({
openModal,
handleCancel
}));
// ...组件内部逻辑不变
};
export default connect((state) => state, actions)(ClModalForm);
// 父组件使用方式
function Parent() {
const ref = useRef<ModalHandles>(null);
return <ClModalForm innerRef={ref} />;
}
方案四:使用 withRef 选项(旧版 React Redux):
// 适用于 React Redux 5.x 及以下版本
const ConnectedClModalForm = connect(
(state) => state,
actions,
null,
{ withRef: true } // 旧版配置
)(forwardRef(ClModalForm));
export default ConnectedClModalForm;