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

React Native中如何实时更新NV内存写入的停留点列表

问题根源

你遇到的hooks must be used inside function components错误,本质是违反了React Hooks的强制调用规则:
所有Hook(包括useRecoilState这类第三方库提供的Hook)只能在React函数组件、自定义Hook的顶层作用域调用,禁止在普通工具函数、异步回调、类方法、循环/条件判断分支中调用。
你直接在全局异步函数LocationChange里调用useRecoilState,这个函数既不是React组件也不是自定义Hook,自然会触发报错,和你把函数放在类外部没有关系。之前使用Hookstate时遇到的难以定位的错误,大概率也是同类调用规则问题导致的。

可直接落地的实现方案

核心思路是把Hook调用严格限制在React组件上下文内,不要在全局工具逻辑中直接触碰Hook API,具体调整分三步:

1. 改造Geolocation类,支持传入位置更新回调

不要在类内部硬编码全局的位置处理逻辑,把回调作为参数传入配置方法,提高逻辑灵活性:

export default class Geolocation {
  static userStopped = true;
  static userOnFoot = true;

  static config(onLocationUpdate) {
    BackgroundGeolocation.onLocation(location => {
      // 收到新位置后执行外部传入的回调
      onLocationUpdate(location);
    }, error => {
      console.log("[location] ERROR: ", error);
    });
    // 保留你原有的其他config逻辑
  }
  // 保留你原有的其他类方法
}

2. 改造位置处理函数,移除所有Hook调用

把原来的LocationChange改成接收状态更新方法作为参数的普通工具函数,函数内部不要出现任何Hook调用:

// 普通工具函数,禁止调用任何Hook
export const handleLocationUpdate = async (newLocation, setStops) => {
  console.log('New location: ', newLocation);
  const newLatitude = newLocation.coords.latitude;
  const newLongitude = newLocation.coords.longitude;
  const isoStr = newLocation.timestamp;

  // 转换ISO时间戳为毫秒级时间戳
  const date = new Date(isoStr);
  const timestamp = date.getTime();

  // 调试阶段写死的判断逻辑可后续替换为正式业务逻辑
  const onFoot = true;
  const stopped = true;
  const closestClient = await GetClosestClient(newLatitude, newLongitude);
  const name = closestClient === 'nenhum'? 'Desconhecido': closestClient.name;

  const sameStop = await StopsDB.isSameStop(newLatitude, newLongitude);
  await StopsDB.saveAsLastStop(timestamp, closestClient.name, newLatitude, newLongitude);

  console.log('onFoot: ', onFoot, ' stopped: ', stopped, ' sameStop: ', sameStop);
  if(onFoot && stopped && sameStop){
    console.log('Saving this location! ----------------------',
      '\nName:........', name,
      '\nLatitude:....', newLatitude,
      '\nLongitude:...', newLongitude,
      '\nTime Stamp:..', timestamp);
    await StopsDB.registerStop(timestamp, name, newLatitude, newLongitude);
    const allStops = await StopsDB.getAll();
    // 调用传入的状态更新方法,触发列表重渲染
    setStops(allStops);
  } else {
    console.log('Do NOT save this new location!');
  }
}

3. 在停留点列表页面组件中绑定逻辑

在你渲染停留点列表的页面组件顶层调用Hook,在组件挂载生命周期中初始化位置监听,保证状态更新逻辑始终在React上下文中执行:

import { useEffect } from 'react';
import { View, FlatList, Text } from 'react-native';
import { useRecoilState } from 'recoil';
import Geolocation from './你的Geolocation文件路径';
import { handleLocationUpdate } from './你的工具函数文件路径';
import { stopsList } from './你的Recoil atom定义路径';

export default function StopsListScreen() {
  // 仅在组件顶层调用Hook
  const [stops, setStops] = useRecoilState(stopsList);

  useEffect(() => {
    // 组件挂载时初始化位置监听,传入状态更新方法
    Geolocation.config((newLocation) => {
      handleLocationUpdate(newLocation, setStops);
    });

    return () => {
      // 组件卸载时在这里添加BackgroundGeolocation监听移除逻辑,避免内存泄漏
    }
  }, []);

  // 直接使用stops状态渲染列表,状态更新后会自动触发重渲染
  return (
    <FlatList
      data={stops}
      renderItem={({item}) => (
        <View>
          <Text>{item.name}</Text>
          <Text>{item.latitude}, {item.longitude}</Text>
        </View>
      )}
      keyExtractor={item => item.timestamp.toString()}
    />
  )
}
避坑提醒
  • 不管是用Recoil、Hookstate还是Redux,只要是遵循React Hooks设计的API,都必须遵守Hook调用规则,否则一定会抛错。后续写代码只要记住:名字以use开头的函数,只能在React函数组件/自定义Hook的最外层调用,不要放在其他任何地方。
  • 如果后续业务复杂不想层层传递set方法,可以再去了解Recoil的useRecoilCallback或者全局状态操作API,但作为初学者,上面的传参方案逻辑最透明,最不容易出现难以排查的隐式错误。
  • 位置监听属于全局副作用,一定要在组件卸载时添加移除逻辑,否则会造成内存泄漏、重复触发回调的问题。

内容的提问来源于stack exchange,提问作者eagle-fries

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 00:33:35