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

React BLE Provider中useCallback循环依赖问题的解决咨询

解决BLE React Provider中的connect与onDisconnected循环依赖问题

你遇到的是典型的React Hooks循环依赖问题:connect 依赖 onDisconnected,而 onDisconnected 又依赖 connect,导致两个useCallback的依赖形成闭环,同时去掉useCallback又会触发ESLint关于依赖变化的警告。下面是几种实用的解决办法:

方法一:用useRef保存函数引用(最直接)

利用useRef的引用稳定性,让onDisconnected始终调用最新版本的connect,同时不用把connect加入它的依赖列表:

const BLEDataContext = createContext<{
  bleData: number | null;
}>({
  bleData: null
});

export const BLEDataProvider = ({ children }) => {
  const [bleData, setBleData] = useState<number | null>(null);
  // 创建ref保存connect的最新引用
  const connectRef = useRef<typeof connect>(null);

  const connect = useCallback(async () => {
    const device = await navigator.bluetooth.requestDevice({ 
      filters, 
      optionalServices, 
    });
    // [...]
    device.addEventListener('gattserverdisconnected', onDisconnected);
    return device;
  }, [onDisconnected, /* 其他依赖 */]);

  // 用useEffect更新ref,确保connectRef始终指向最新的connect
  useEffect(() => {
    connectRef.current = connect;
  }, [connect]);

  const onDisconnected = useCallback((event: any) => {
    // [...]
    if (someCondition) {
      // 调用ref里保存的最新connect
      connectRef.current?.();
    }
  }, [/* 这里只放除了connect之外的其他依赖 */]);

  return (
    <BLEDataContext.Provider value={{ bleData }}>
      {children}
    </BLEDataContext.Provider>
  );
}

这样一来,onDisconnected的依赖列表里不再需要connect,循环依赖直接被打破,同时ESLint警告也会消失——因为connectRef是稳定的引用,不会随渲染变化。

方法二:用useReducer解耦逻辑

如果你的连接逻辑比较复杂,可以把connect和onDisconnected的逻辑迁移到useReducer中,利用dispatch的稳定性来避免依赖循环:

const BLEDataContext = createContext<{
  bleData: number | null;
}>({
  bleData: null
});

// 定义reducer处理连接相关动作
const bleReducer = (state, action) => {
  switch (action.type) {
    case 'SET_DATA':
      return { ...state, bleData: action.payload };
    case 'RECONNECT':
      // 在这里执行重连逻辑
      action.payload.connect();
      return state;
    default:
      return state;
  }
};

export const BLEDataProvider = ({ children }) => {
  const [state, dispatch] = useReducer(bleReducer, { bleData: null });
  const { bleData } = state;

  const connect = useCallback(async () => {
    const device = await navigator.bluetooth.requestDevice({ 
      filters, 
      optionalServices, 
    });
    // [...]
    const handleDisconnected = () => {
      if (someCondition) {
        // 用dispatch触发重连,dispatch是稳定的,不会变
        dispatch({ type: 'RECONNECT', payload: { connect } });
      }
    };
    device.addEventListener('gattserverdisconnected', handleDisconnected);
    return device;
  }, [dispatch, /* 其他依赖 */]);

  // 初始化连接(如果需要)
  useEffect(() => {
    connect();
  }, [connect]);

  return (
    <BLEDataContext.Provider value={{ bleData }}>
      {children}
    </BLEDataContext.Provider>
  );
}

这里dispatch是React保证稳定的引用,所以connect的依赖列表里只需要dispatch,而handleDisconnected内联在connect中,不需要额外的useCallback,彻底避免了循环依赖。

方法三:调整监听逻辑,避免重复绑定

如果之前尝试内联onDisconnected没成功,可能是因为每次connect都会创建新的监听函数,导致旧的监听还存在。可以在连接前移除旧的监听,同时把onDisconnected的逻辑内联,并且不依赖外部的connect:

const BLEDataContext = createContext<{
  bleData: number | null;
}>({
  bleData: null
});

export const BLEDataProvider = ({ children }) => {
  const [bleData, setBleData] = useState<number | null>(null);
  // 保存设备引用,方便移除监听
  const deviceRef = useRef<BluetoothDevice | null>(null);

  const connect = useCallback(async () => {
    // 如果之前有设备,先移除旧的监听
    if (deviceRef.current) {
      deviceRef.current.removeEventListener('gattserverdisconnected', handleDisconnected);
    }

    const device = await navigator.bluetooth.requestDevice({ 
      filters, 
      optionalServices, 
    });
    deviceRef.current = device;

    // 内联断开处理函数,直接调用connect
    async function handleDisconnected(event: any) {
      // [...]
      if (someCondition) {
        connect();
      }
    }

    device.addEventListener('gattserverdisconnected', handleDisconnected);
    return device;
  }, [/* 这里只放除了onDisconnected之外的依赖 */]);

  return (
    <BLEDataContext.Provider value={{ bleData }}>
      {children}
    </BLEDataContext.Provider>
  );
}

这种方式把handleDisconnected内联在connect里,虽然每次connect都会创建新的函数,但我们在连接前移除了旧的监听,避免内存泄漏,同时因为函数内联,不需要把connect加入外部依赖,也打破了循环。

内容的提问来源于stack exchange,提问作者Aurélien Gasser

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 21:12:09