React forwardRef与回调ref问题:onCreateOrUpdate中drawRef为undefined
React Refs 问题:onCreateOrUpdate 中 drawRef 为何为 undefined?
本文与Mapbox无关,无需地图相关知识,仅涉及React refs问题
我在项目中使用react-map-gl(Mapbox的React封装)搭配@mapbox/mapbox-gl-draw实现地图几何图形绘制功能,包含两个组件:DrawControl(暴露绘制功能)和AllPastures(渲染地图并管理绘制图形的增删改)。
当用户绘制多边形时,DrawControl的onCreate方法会触发执行onCreateOrUpdate。我通过forwardRef将Draw实例从DrawControl传递到AllPastures。
DrawControl 组件代码
export const DrawControl = React.forwardRef<MapboxDraw, DrawControlProps>( (props: DrawControlProps, ref) => { const drawRef: MapboxDraw = useControl<MapboxDraw>( () => new MapboxDraw(props), ({ map }) => { map.on('draw.create', props.onCreate); map.on('draw.update', props.onUpdate); map.on('draw.delete', props.onDelete); map.on('draw.modechange', props.onModeChange); }, ({ map }) => { map.off('draw.create', props.onCreate); map.off('draw.update', props.onUpdate); map.off('draw.delete', props.onDelete); map.off('draw.modechange', props.onModeChange); }, { position: props.position, } ); // 向外暴露drawRef实例 React.useImperativeHandle(ref, () => drawRef, [drawRef]); return null; } );
AllPastures 组件部分代码
export const AllPastures = () => { ... const [drawRef, setDrawRef] = React.useState<MapboxDraw>(); // 通过回调Ref响应drawRef变化 const onDrawRef = React.useCallback((ref: MapboxDraw | null) => { if (ref) { setDrawRef(ref); } }, []); React.useEffect(() => { // 这里drawRef正常,不为undefined console.log('useEffect drawRef when app is loading', drawRef); }, [map, drawRef]); const onCreateOrUpdate = (e: { features: Feature[] }) => { // 为什么这里drawRef是undefined? console.log('drawRef under onCreateOrUpdate method', drawRef); }; ... return ( <DrawControl ref={onDrawRef} position="top-right" displayControlsDefault={false} controls={{ polygon: true, trash: true, }} defaultMode="simple_select" onCreate={onCreateOrUpdate} onUpdate={onCreateOrUpdate} /> ); };
问题原因
这是典型的React闭包陷阱:
onCreateOrUpdate函数在组件首次渲染时创建,它捕获了当时的drawRef状态值(初始为undefined)。- 后续
onDrawRef回调更新了drawRef的状态,但DrawControl中绑定到地图事件的onCreate依然是首次渲染时的onCreateOrUpdate版本,这个版本里的drawRef始终是初始的undefined。 - 另外,用
useState存储ref实例本身就不合适——ref实例是稳定的引用对象,不需要用状态存储,状态更新会触发组件重渲染,反而容易加剧闭包问题。
解决方案
方案一:用useRef替代useState存储Draw实例(推荐)
useRef的current属性不会受闭包影响,能始终获取到最新的实例:
export const AllPastures = () => { // 用useRef存储Draw实例,替代useState const drawRef = React.useRef<MapboxDraw>(); // 回调Ref直接赋值给current const onDrawRef = React.useCallback((ref: MapboxDraw | null) => { if (ref) { drawRef.current = ref; } }, []); const onCreateOrUpdate = (e: { features: Feature[] }) => { // 直接访问current属性,获取最新实例 console.log('drawRef under onCreateOrUpdate method', drawRef.current); }; // ... 其他代码保持不变 return ( <DrawControl ref={onDrawRef} position="top-right" displayControlsDefault={false} controls={{ polygon: true, trash: true, }} defaultMode="simple_select" onCreate={onCreateOrUpdate} onUpdate={onCreateOrUpdate} /> ); };
方案二:用useCallback包裹onCreateOrUpdate并添加依赖
如果一定要使用useState,需要让onCreateOrUpdate依赖drawRef,确保每次drawRef更新时函数能重新创建:
export const AllPastures = () => { const [drawRef, setDrawRef] = React.useState<MapboxDraw>(); const onDrawRef = React.useCallback((ref: MapboxDraw | null) => { if (ref) { setDrawRef(ref); } }, []); // 用useCallback包裹,添加drawRef作为依赖 const onCreateOrUpdate = React.useCallback((e: { features: Feature[] }) => { console.log('drawRef under onCreateOrUpdate method', drawRef); }, [drawRef]); // ... 其他代码保持不变 return ( <DrawControl ref={onDrawRef} position="top-right" displayControlsDefault={false} controls={{ polygon: true, trash: true, }} defaultMode="simple_select" onCreate={onCreateOrUpdate} onUpdate={onCreateOrUpdate} /> ); };
注意:此方案会导致每次drawRef更新时,onCreateOrUpdate重新创建,进而触发DrawControl中地图事件的解绑与重新绑定,性能上不如方案一高效。
内容的提问来源于stack exchange,提问作者Joel
相关产品推荐
相关产品推荐

