前言
React.useImperativeHandle
不知道这个hook你们使用的频率如何,我反正是经常用在业务的Modal组件上。今天查看react新的官方文档发现,我这种使用方式居然是react官方不推荐的,这让我不禁思考起来"难道我一直都踩在陷阱里?"
再仔细翻阅文档,我并没有详细地看到官方告诉我,我这种用法到底有什么问题?单凭下面几句无法说服我,我反而觉得ModalRef.open()
这种写法更加优雅。
我的写法
在以下代码案例中,我举例一个Modal组件,通过useImperativeHandle向外expose了一个方法open。 该方法中接受一个传参id(而不是通过props传入),同时执行了request等副作用。
tsx
const _IssueModal = (props: IProps, ref: Ref<IRef>) => {
//变量声明、解构
const {} = props;
const { issueApi, roomTreeApi } = settingScreen$;
//组件状态
const uiIdRef = useRef('');
const deviceIdsRef = useRef<string[]>([]);
const [open, setOpen] = useState(false);
//网络IO
//数据转换
const options = transformOptions(roomTreeApi.data || []);
//逻辑处理函数
const handleIssue = async () => {
issueApi.request();
};
const handleReset = () => {
uiIdRef.current = '';
deviceIdsRef.current = [];
setOpen(false);
};
//组件Effect
useImperativeHandle(ref, () => ({
open: (id: string) => {
if (!id) return;
uiIdRef.current = id;
setOpen(true);
roomTreeApi.request();
},
}));
//组件渲染
return (
<Modal
open={open}
title='主题资源包下发'
onCancel={handleReset}
onOk={handleIssue}
destroyOnClose
maskClosable={false}
okButtonProps={{
disabled: !deviceIdsRef.current.length,
}}
>
<p>请选择要更新的设备</p>
<Cascader
className={styles.CascaderSelect}
options={options}
/>
</Modal>
);
};
//props类型定义
interface IProps {}
interface IRef {
open: (id: string) => void;
}
//prop-type定义,可选
const IssueModal = observer<IProps, IRef>(_IssueModal, { forwardRef: true });
export { IssueModal };
使用ModalRef.open
的好处
- 可读性好
我把相关的业务逻辑与state,都封装在一个单独的组件里,而不是把所有逻辑平铺到父组件。 特别是当前业务场景有多个Modal,如果采用传统写法的代码就像拉面条,每个open状态还需要单独命名,相当痛苦且不美观。
- 解耦
父组件不需要知道我这个Modal是干什么的,也不应该直接修改Modal的内部状态,只需要按照声明的接口IRef调用
open
就可以。
- 复用
上述例子中,
IssueModal
是个具有完整业务逻辑的组件,其他地方需要复用这段逻辑,直接引用Ref就可以。
tsx
// 使用React.ComponentRef 获取IRef类型,不要再写any了!!!
const issueModalRef = useRef<ComponentRef<typeof IssueModal>>(null);
// ...
const handleClick = (record) => {
issueModalRef.current?.open(record.id);
}
// ...
<IssueModal ref={issueModalRef} />
同道中人
google了一下,发现了不少人也和我一样是这样写Modal组件的。举个几个例子:
- Vue3 expose官方例子
- modal.open() - Imperative Component API in React
- Why imperative code using refs should be avoided?
后话
这篇文章并不是说我的写法是那么牛逼,而是解答我心中疑惑,"React官方推荐的一定是对吗?有没有更好的写法?当面对不同的业务场景我应该选择哪种写法会更好?"
感谢React官方提供那么棒的前端开源框架,只是基于OOP的开发思维&经验,面对我的业务场景时,我觉得使用useImperativeHandle
会更优雅。