You need to enable JavaScript to run this app. 自定义hook内使用useEffect导致React Native应用多处调用时冻结
优惠活动
大模型
产品
解决方案
定价
更多

自定义hook内使用useEffect导致React Native应用多处调用时冻结

基于AsyncStorage封装的自定义Hook触发应用冻结问题修复

问题复现

开发了一个基于AsyncStorage封装的配置读取自定义hook,原始实现代码如下:

// Takes the key/name of the setting to retrieve
export const useSettings = (key) => {

  // stores the json string result in the setting variable
  const [setting, setSetting] = useState("");

  const deviceStorage = useContext(DeviceStorageContext);

  useEffect(() => {
    getValue()
     .then(value => setSetting(value));
  }, []);

  // gets value from my custom api that uses Async-Storage to handle r/w of the data.
  const getValue = async () => await deviceStorage.getValueStored(key);
  
  const setValue = async (value) => { 
      await deviceStorage.setValueStored(key, value);
      getValue().then(value => setSetting(value));
  };

  
  const removeValue = async () => { }

  return [setting, { setValue,removeValue }];
};

该hook在Main.jsx中调用符合预期,对应代码如下:

const Main = () => {
  const [units, operations] = useSettings('units');

  useEffect(() => {
    const initSettings = async () => {
      if (units) {
       console.log(units)
       return;
     }
      await operations.setValue({ pcs: 1, box: 20 });
    };
    initSettings();
  }, []);

在Form.jsx中仅调用该hook就会导致应用完全冻结,Form.jsx的调用代码如下:

const FormView = ({ handleReset, handleSubmit }) => {
  const [setting,] = useSettings('units');

移除hook内的useState和useEffect后问题消失,但不希望在业务代码中随处编写async/await处理异步存储逻辑。

根因分析

  • 内部异步方法未做引用稳定处理,每次组件渲染都会生成新的函数引用,结合未补全的useEffect依赖项,容易触发不必要的重复执行
  • 返回的操作对象每次渲染生成新的引用,若被其他依赖项监听会触发连锁重渲染,极端情况下会进入无限重渲染循环导致应用冻结
  • 未做组件卸载判断,异步回调执行已卸载组件的setState会触发额外的性能损耗,严重时会引发运行异常
  • 未做异常捕获,存储操作抛出的未处理异常会导致应用运行阻塞

修复方案

优化后完整Hook代码

import { useState, useEffect, useCallback, useMemo, useContext } from 'react';
import { DeviceStorageContext } from './你的context文件路径';

export const useSettings = (key) => {
  // 初始值设为null,区分「未加载」和「空值」状态
  const [setting, setSetting] = useState(null);
  const deviceStorage = useContext(DeviceStorageContext);

  // 用useCallback固定getValue引用,仅在依赖变化时重建
  const getValue = useCallback(async () => {
    try {
      const value = await deviceStorage.getValueStored(key);
      // 适配AsyncStorage的字符串存储特性,自动反序列化对象类型值
      return typeof value === 'string' ? JSON.parse(value) : value;
    } catch (e) {
      console.error(`获取配置${key}失败:`, e);
      return null;
    }
  }, [key, deviceStorage]);

  // 用useCallback固定setValue引用
  const setValue = useCallback(async (value) => {
    try {
      // 自动序列化对象类型值适配存储
      const storedValue = typeof value === 'object' ? JSON.stringify(value) : value;
      await deviceStorage.setValueStored(key, storedValue);
      const latestValue = await getValue();
      setSetting(latestValue);
    } catch (e) {
      console.error(`保存配置${key}失败:`, e);
    }
  }, [key, deviceStorage, getValue]);

  // 用useCallback固定removeValue引用
  const removeValue = useCallback(async () => {
    try {
      await deviceStorage.removeValueStored(key);
      setSetting(null);
    } catch (e) {
      console.error(`删除配置${key}失败:`, e);
    }
  }, [key, deviceStorage]);

  useEffect(() => {
    let isMounted = true;
    getValue().then(value => {
      // 组件卸载后跳过setState,避免内存泄漏警告
      if (isMounted) setSetting(value);
    });
    // 注册清理函数
    return () => {
      isMounted = false;
    };
  // 补全useEffect依赖项
  }, [getValue]);

  // 用useMemo固定返回的操作对象引用,避免触发额外重渲染
  const operations = useMemo(() => ({
    setValue,
    removeValue
  }), [setValue, removeValue]);

  return [setting, operations];
};

额外检查点

  • 确认DeviceStorageContext的实现不会在存储更新时无差别刷新所有消费者,可通过拆分context、memo优化context value的方式避免全局重渲染
  • 确认deviceStorage的所有方法都正确返回Promise,不存在永远pending的异常情况
  • 确认FormView组件在DeviceStorageContext的作用域内,可正常获取到deviceStorage实例

内容的提问来源于stack exchange,提问作者foreign-native

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.15 10:36:05