前言

在开发React Native应用时,我们经常会遇到需要在多个组件间共享状态和数据的场景。如果处理不当,很容易出现数据不一致、重复初始化等问题。本文将通过一个真实的项目案例,展示如何使用单例模式来解决这些问题。

问题背景

在我们的LoveConnect项目中,用户信息管理是一个核心功能。我们有一个InfoManager类来处理用户信息的获取、更新和存储。最初的实现如下:

// 原始实现 - 存在问题的版本
export const getInfoManager = (): InfoManager => {
  const isDevelopment = mod === 'development';
  if (isDevelopment) {
    return new InfoManagerText();  // 每次都创建新实例
  } else {
    return new InfoManagerImpl();  // 每次都创建新实例
  }
};

这种实现方式看似简单,但在实际使用中暴露出了严重的问题。

遇到的问题

1. 多实例数据不一致

当不同的React组件调用getInfoManager()时,每次都会创建新的实例:

// ProfilePage.tsx
const infoManager = getInfoManager(); // 实例A

// EditNamePage.tsx  
const infoManager = getInfoManager(); // 实例B

// EditPhonePage.tsx
const infoManager = getInfoManager(); // 实例C

每个实例都有自己的内存缓存和初始化状态:

class InfoManagerImpl implements InfoManager {
  private currentInfo: Info | null = null;  // 每个实例都有独立的缓存
  private initialized = false;              // 每个实例都有独立的状态
}

2. 重复初始化问题

每个新实例都需要重新从AsyncStorage加载数据,造成不必要的性能开销:

async initialize(): Promise<void> {
  if (this.initialized) return;
  try {
    // 每个实例都要重新从存储加载数据
    const stored = await AsyncStorage.getItem(STORAGE_KEY);
    if (stored) {
      this.currentInfo = JSON.parse(stored);
    }
    this.initialized = true;
  } catch (error) {
    console.error('初始化用户信息失败:', error);
  }
}

3. 状态同步问题

用户在一个页面更新信息后,其他页面的实例可能仍然显示旧数据,导致UI不一致。

解决方案:单例模式

实现思路

单例模式确保一个类只有一个实例,并提供全局访问点。对于我们的场景,这意味着:

  1. 整个应用只有一个InfoManager实例
  2. 所有组件共享同一份数据和状态
  3. 只需要初始化一次

    具体实现

    // 单例实例存储
    let infoManagerInstance: InfoManager | null = null;
    
    export const getInfoManager = (): InfoManager => {
      // 如果已经有实例,直接返回
      if (infoManagerInstance) {
        return infoManagerInstance;
      }
      // 首次调用时创建实例
      const isDevelopment = mod === 'development';
      if (isDevelopment) {
        infoManagerInstance = new InfoManagerText();
      } else {
        console.log('使用生产环境信息管理器');
        infoManagerInstance = new InfoManagerImpl();
      }
      return infoManagerInstance;
    };
    
    // 重置单例实例的方法(用于测试或特殊情况)
    export const resetInfoManager = (): void => {
      infoManagerInstance = null;
    };

    在React组件中的使用

    现在所有组件都会获得同一个实例:

    // ProfilePage.tsx
    const infoManager = getInfoManager(); // 获取单例实例
    
    const ProfilePage = () => {
      const [info, setInfo] = useState<any>({});
      const fetchInfo = useCallback(async () => {
        const info = await infoManager.getInfo(); // 使用单例实例
        setInfo(info);
      }, []);
      // 使用useFocusEffect确保页面聚焦时刷新数据
      useFocusEffect(
        useCallback(() => {
          fetchInfo();
        }, [fetchInfo])
      );
      // ...
    };

    优势分析

    1. 数据一致性保证

// 现在所有地方都使用同一个实例
const manager1 = getInfoManager(); // 同一个实例
const manager2 = getInfoManager(); // 同一个实例
console.log(manager1 === manager2); // true

2. 性能优化

  • 减少内存占用:只创建一个实例
  • 避免重复初始化:只从AsyncStorage加载一次数据
  • 减少网络请求:缓存的数据在所有地方共享

3. 状态同步

当用户在任何页面更新信息时,所有其他页面都能立即看到最新数据:

// EditNamePage.tsx - 更新姓名
await infoManager.updateName(newName);

// ProfilePage.tsx - 自动获取到最新数据
const info = await infoManager.getInfo(); // 包含最新的姓名

背景:
后端完好。expo启动在安卓模拟器。

问题:
本地api地址使用localhost报TypeError: Network request failed

分析
在Android模拟器中, localhost:8080 或 127.0.0.1:8080 指向的是 模拟器内部 ,而不是宿主机(电脑)。这就是为什么会出现 Network request failed 错误的根本原因。

解决:
使用电脑的实际IP地址,
在命令行运行 ipconfig (Windows)查看你的IP地址,将api地址中localhost改为实际的ip

cloudflare 解析的域名 可以使用泛域名的ssl证书,并且可以直接重定向至https,有了这个之后如果使用lnmp来新建站点,可以不需要勾选添加ssl,并且在实际使用中,会默认使用https连接。

但是对于wordpress来说,新建文章需要站点的url和数据库中的一致。因为这个https实际上不是这个子域的,所以在安装的时候,默认会给你写成http。所以只需要将http改为https即可。

202508291700

需要将 站点地址 改为 https即可。
如果不小心将wordpress地址也改为了https,就好出现重定向过多,导致无法进入后台
这个时候可以连上数据库,修改wp_options表中的site_url字段将https改回http即可。

昨天晚上的时候在美t上点了三杯(瓶, 500ml + 500ml + 300ml)鸡尾酒,才40多。是白俄罗斯,莫吉托还有一杯说是伏特加和朗姆的现调。比我直接去店里喝要便宜不少。
喝三杯确实容易脑袋昏沉,想睡觉。

华五还算是比较顺利的进行。前端因为打算用react-native。所以就是我一个人做。是挺累的,不过成就感还是有的。也算是技能 + 1。

早上起来的时候还是有点反胃,不想吃东西。靠喝咖啡续命。

最近就还好。快要开学了,就只有华五一件算是比较急的事情。其他就都没有了。然后说到项目就想起来我这个月的补贴还没到,唉。希望不是老师忘了。

因为有华五,然后比我想的要花时间的多,所以暂时就没有去找实习,虽然最近开始找我的公司变多了。

说到找实习,其实是当时集训没结束,然后实验室项目没做完,接的一个烟厂的外包刚刚把技术债填好的时候。那个时候还是很忙,压力比较大。所以就不知道为什么萌生出了去找实习的念头,可能也是缺钱了。后面又去找导师,被导师说了一顿,其实她还是想让我好好搞保研。我被说服了,暂时。因为我也没有什么前进的动力或者上升的目标。突如其来的华五把这个空缺填满了。

华五本来是不想参加的,原因是我上次带队的大创因为创意不行,只是校级,后面在做的时候,又因为是带的5个都没什么实际开发经验的,前后端后面重构的时候大部分的工作都是我来做的,做的我很痛苦。其实也是因为这个做的东西恰好和实验室需要的一个项目重了,所以才有动力做,不然大概率也是不想做的。

后面是因为和我一起做大创的一个女生想参加,事实当时拉她来做大创就是看中了她很认真,并且也愿意学。当时我的想法很简单,觉得会不会不重要,认真负责还是比较重要。所以我拉她来做大创,并且用加分和获奖来作为萝卜。不过很可惜最后我没有兑现这个承诺。当时在做大创的时候有提过一嘴要一起参加华五的事。她可能就一直记着。当我有点失落,看到大创的情况,包括就是后面实施的时候,我是想要打个商量,打算就不去华五了。后面被劝住了,我说,好,那你来想创意,做文档。她说,行。于是后面我继续带队打华五,并且这次比较幸运找的另外三个人也是从华五报名表里面找的,至少人不摆,还有一个有后端经验,是springboot。这也是我这次不做后端而是做前端的一个主要原因。

时间线拉到昨天晚上,又是简单的讨论了一下ai的部分,短暂的周末又要结束了,实际上真的开始做华五之后发现其实问题和困难比我想的要多。晚上学长也是又去出差,浅浅的待一下午之后,又剩我一个人,我看着电脑屏幕发呆,想去酒吧了。不过外面下雨,想到最近一直没有心情做饭,已经点了一周外卖了,所以我想到看有没有鸡尾酒外卖,还真有,算是填补了我不能去外边的遗憾。我把送的吸管怼到瓶子里,一口一口,安静的嗦着。小半瓶莫吉托后不知所措的内心渐渐平静,我拿着吉他,对着简谱,在弹。又是一周。

好惆怅。不知道后面华五能不能参加,大创也不好意思再打扰他们做了,今天开了一个简短的会。
不过只有2个人来。
主要还是说下后面不用跟进了,让他们把已经做好的东西传到仓库里面,然后后面的我自己来做就好了。
总有种茫然感。不知道有没有力气,也使不出劲。一种不知道要做什么的感觉。倒是不累,只是不知道做什么,可能是想做的太多了。
这几天也没做什么事情。
好想找个人来安慰我下。