1. 作用域差异

普通CSS:

  • 全局作用域,所有样式都是全局的
  • 容易出现样式冲突和覆盖问题
/* styles.css */
.button {
    background-color: blue;
}
// 使用普通CSS
import './styles.css';
<button className="button">按钮</button>

CSS模块:

  • 局部作用域,样式只在当前组件内生效
  • 自动生成唯一的类名,避免冲突
/* Button.module.css */
.button {
    background-color: blue;
}
// 使用CSS模块
import styles from './Button.module.css';
<button className={styles.button}>按钮</button>

2. 类名生成机制

普通CSS:

  • 类名保持原样:.buttonbutton

CSS模块:

  • 自动生成哈希类名:.buttonButton_button__2x3kl
  • 确保每个组件的样式独立

3. 使用方式

普通CSS:

// 直接使用字符串
<div className="container header active">

CSS模块:

// 通过styles对象引用
<div className={styles.container}>
<div className={`${styles.header} ${styles.active}`}>

4. 优缺点对比

特性普通CSSCSS模块
学习成本中等
样式冲突容易发生几乎不会
代码维护困难(大项目)容易
性能一般更好(按需加载)
调试容易稍复杂(类名被哈希)

5. 适用场景

普通CSS适合:

  • 小型项目
  • 全局样式(重置样式、主题等)
  • 第三方库的样式覆盖

CSS模块适合:

  • 中大型项目
  • 组件化开发
  • 需要样式隔离的场景

useFocusEffect

useFocusEffect 是 React Navigation 提供的一个 Hook,专门用于处理页面获得焦点时的副作用。

基本用法:

import { useFocusEffect } from '@react-navigation/native';
import { useCallback } from 'react';

function MyScreen() {
  useFocusEffect(
    useCallback(() => {
      // 页面获得焦点时执行的代码
      console.log('页面获得焦点');
      
      // 返回清理函数(可选)
      return () => {
        console.log('页面失去焦点');
      };
    }, [])
  );
}

特点:

  • 每次页面获得焦点时都会执行
  • 支持清理函数,页面失去焦点时执行
  • 常用于数据刷新、订阅管理等场景

useCallback

useCallback 是 React 提供的性能优化 Hook,用于缓存函数引用。

基本语法:

const memoizedCallback = useCallback(
  () => {
    // 函数逻辑
  },
  [dependency1, dependency2] // 依赖数组
);

工作原理:

  • 只有当依赖数组中的值发生变化时,才会重新创建函数
  • 如果依赖数组为空 [],函数只会创建一次
  • 返回的是函数的缓存版本

使用场景:

  1. 防止子组件不必要的重渲染:

    function Parent() {
      const [count, setCount] = useState(0);
      
      // 没有 useCallback - 每次渲染都创建新函数
      const handleClick = () => {
     console.log('clicked');
      };
      
      // 使用 useCallback - 函数引用保持不变
      const memoizedHandleClick = useCallback(() => {
     console.log('clicked');
      }, []);
      
      return <Child onClick={memoizedHandleClick} />;
    }
  2. 与其他 Hook 配合使用:

    function MyComponent() {
      const [data, setData] = useState(null);
      
      const fetchData = useCallback(async () => {
     const result = await api.getData();
     setData(result);
      }, []); // 空依赖数组,函数只创建一次
      
      useEffect(() => {
     fetchData();
      }, [fetchData]); // fetchData 不会导致无限循环
    }

性能考虑

useCallback 的使用原则:

考虑使用:

  • 传递给子组件的函数
  • 作为其他 Hook 的依赖
  • 计算成本较高的函数

不考虑使用:

  • 简单的事件处理函数(如果没有传递给子组件)
  • 过度使用(useCallback 本身也有开销)

示例对比:

// 不需要 useCallback
function SimpleComponent() {
  const handleClick = () => {
    console.log('simple click');
  };
  
  return <button onClick={handleClick}>Click</button>;
}

// 需要 useCallback
function ComplexComponent({ children }) {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    // 复杂逻辑或传递给子组件
    setCount(prev => prev + 1);
  }, []);
  
  return (
    <div>
      {React.Children.map(children, child => 
        React.cloneElement(child, { onClick: handleClick })
      )}
    </div>
  );
}

总结

  • useFocusEffect:处理页面焦点变化的副作用,常用于数据刷新
  • useCallback:缓存函数引用,优化性能,防止不必要的重渲染
  • 结合使用:在导航场景中实现高效的数据刷新机制

这两个 Hook 在 React Native 应用中非常实用,特别是在处理页面导航和性能优化方面。

前言

在开发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即可。