You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 20:45:43