使用React函数组件开发的过程中会遇到父组件调用子组件的方法或者属性的场景,首次先说怎么通过React.forwardRef来将子组件的属性或者方法暴露给父组件
子组件
import { Box, Typography } from "@mui/material"; import { forwardRef, useImperativeHandle } from "react"; interface LocationChildProps { data: string; } export interface LocationChildRef { sayType(): void; } const LocationChild = forwardRef<LocationChildRef, LocationChildProps>((props, ref) => { useImperativeHandle(ref, () => ({ sayType() { console.log("子组件的data是 " + typeof props.data); }, })); return ( <Box> <Typography>{typeof props.data}</Typography> </Box> ); }); export default LocationChild;
在子组件中我们需要接受一个key为data的props,然后在子组件中展示这个值,并且通过useImperativeHandle向外暴露一个sayType的方法, 最后用forwardRef将子组件封装然后暴露出去,这里forwardRef的作用就是包装该组件为一个可以通过Ref访问的组件。
父组件
import { Button } from "@mui/material"; import { useRef } from "react"; import ConfigDetailContainer from "../options/ConfigDetailContainer"; import LocationChild, { LocationChildRef } from "./LocationChild"; export default function DeviceLocation() { const locationChildRef = useRef<LocationChildRef>(); const handleClick = () => { locationChildRef.current.sayType() // 输出: 子组件的type是 string }; return ( <ConfigDetailContainer title="device.configTabs.LOCATION_HISTORY"> <LocationChild ref={locationChildRef} data="asdafaf"></LocationChild> <Button onClick={handleClick}>查看子组件的type的类型</Button> </ConfigDetailContainer> ); }
父组件中需要通过useRef来创建ref并传递给子组件,这样父子组件就建立了连接,父组件可以通过ref来访问子组件中自定义暴露的属性或方法。
这里的操作就是父组件点击按钮控制台打印子组件接收到的data这个prop的类型。
现在新的问题就是我们的父组件传递的data的类型不是固定的,这时候子组件就要将data的类型用泛型来定义,所以这里就有了fowardRef传递泛型参数的问题:
我们可以这样改造子组件,思路就是将这个组件改为工厂hansh的生成模式:
import { Box, Typography } from "@mui/material"; import { forwardRef, useImperativeHandle } from "react"; export interface LocationChildProps<T = string> { data: T; } export interface LocationChildRef { sayType(): void; } const LocationChild = function <T>() { return forwardRef<LocationChildRef, LocationChildProps<T>>((props, ref) => { useImperativeHandle(ref, () => ({ sayType() { console.log("子组件的data是 " + typeof props.data); }, })); return ( <Box> <Typography>{typeof props.data}</Typography> </Box> ); }); }; export default LocationChild;
然后在父组件中使用
import { Button } from "@mui/material"; import { PropsWithRef, useRef } from "react"; import ConfigDetailContainer from "../options/ConfigDetailContainer"; import LocationChild, { LocationChildProps, LocationChildRef } from "./LocationChild"; export default function DeviceLocation() { const locationChildRefString = useRef<LocationChildRef>(); const locationChildRefBoolean = useRef<LocationChildRef>(); const handleClick = () => { locationChildRefString.current.sayType(); locationChildRefBoolean.current.sayType(); }; const LocationChildComponent = LocationChild<string>(); const createComponent = function <T>(props: PropsWithRef<any>, ref: React.MutableRefObject<LocationChildRef>) { const Mycomponent = LocationChild<T>(); return <Mycomponent ref={ref} {...props}></Mycomponent>; }; return ( <ConfigDetailContainer title="device.configTabs.LOCATION_HISTORY"> <LocationChildComponent ref={locationChildRefString} data={"123"}></LocationChildComponent> {createComponent({ data: true }, locationChildRefBoolean)} <Button onClick={handleClick}>查看子组件的type的类型</Button> </ConfigDetailContainer> ); }
我们可以直接调用LocationChild方法生成组件,也可以再度封装为createComponent这样的方法,这样就实现了forwardRef中使用泛型参数的需求。
网上没有找到合适的方案,看了 antd 的源码
const ForwardTable = React.forwardRef(InternalTable) as <RecordType extends object = any>( props: React.PropsWithChildren<TableProps<RecordType>> & { ref?: React.Ref<HTMLDivElement> }, ) => React.ReactElement; // so u can use <Table<{id: string, b: number}> />
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
相关文章: