对useEffect和 useMemo的一些总结与感悟

今天在项目中遇到useEffect和useMemo的一些坑,总结一下。

在现代的react框架编程中,需要摒弃使用Ref来进行状态管理。

如果在一个组件中,重复进行ReactQuery的Query的请求,需要检查其QueryKey是否重合。如:

javascript 复制代码
export function useGetUserWithName(username: string) {
  return useQuery({
    queryKey: ['user', 'data', username],
    queryFn: async () => {
      const res = await http.get<GetUserWithNameApiResponse>(
        getUserWithNamePath + `/${username}`
      );
      return res;
    },
    enabled: !!username,
  });
}

export function useGetAvatar(username: string) {
  return useQuery({
    queryKey: ['user', 'avatar', username],
    queryFn: async () => {
      const res = await http.get<GetUserAvatarApiResponse>(
        GetUserAvatarPath + `/${username}`
      );
      return res;
    },
    enabled: !!username,
  });
}

上面的两个queryKey如果重合,数据则会被先请求的覆盖掉。所以加第二个缓存标识很有必要。

还有enabled,必须要加。加上后在组件中调用接口的时候,就不会陷入死循环,重复去一直调用接口。

那么以上和useEffect、useMemo有什么关系呢?

当使用接口时,必然会有:

kotlin 复制代码
  const { data: avatarResponse } = useGetAvatar(username);
  const { data: userdata } = useGetUserWithName(username);

这时它们都在组件的宏任务中,但是它们的function都是async的,所以宏任务依次调用后会添加到它们自己的异步任务中。

然后就是返回时机,它们返回的时机不一致,会导致数据来的时间不一致。

如果要处理其中的数据,则需要对其进行状态管理。

那么第一步就是拿出里面的数据。而我们知道,一个接口的数据返回出来,可能在深度上太深,所以我们要处理深度,避免直接调它们的data。

那么如何处理深度?

ini 复制代码
const userinfo = useMemo((): GetUserWithNameApiResponse | undefined => {
    return userdata?.data;
}, [userdata]);

这样就会减少数据的深度,我们也能更清晰的拿到数据。

kotlin 复制代码
const avatarUrl = useMemo(() => {
    if (!avatarResponse?.data?.data?.buffer?.data) {
      return undefined;
    }

    try {
      // 提取 buffer 数据(数字数组)
      const bufferData = avatarResponse.data.data.buffer.data;
      const mimeType = avatarResponse.data.data.mimetype || 'image/jpeg';

      // 将数字数组转换为 Uint8Array
      const uint8Array = new Uint8Array(bufferData);

      // 创建 Blob
      const blob = new Blob([uint8Array], { type: mimeType });

      // 创建对象 URL
      return URL.createObjectURL(blob);
    } catch (error) {
      console.error('Failed to convert avatar buffer to URL:', error);
      return undefined;
    }
  }, [avatarResponse]);

上述代码是将图片的数据结构取出,转换为Blob形式。其实也可以转为Base64的数据字符串。

那么useMemo就是进行这种运算的。它会将数据进行处理。

那么useEffect呢?它其实就是处理数据的副作用的。

kotlin 复制代码
useEffect(() => {
    // 只在数据真正改变时才更新,避免无限循环
    if (!username) {
      return;
    }

    // eslint-disable-next-line react-hooks/set-state-in-effect
    setProfile((prev) => ({
      nickname: userinfo?.data.username || username || '',
      email: userinfo?.data.email || prev.email || '',
      phone: userinfo?.data.phone || prev.phone || '',
      bio: userinfo?.data.desc || prev.bio || '',
      avatar: avatarUrl || prev.avatar || null,
    }));
  }, [username, avatarUrl, userinfo]);

可以看到,使用useEffect对数据读取并设置值,这就是数据的"作用"。 需要注意的是,最好在一个数据结构更新时设置一个useEffect统一更新数据,不然会有数据不一致的问题。

当A数据是H结构的一份子,B也是H结构的一份子时,如果使用两个useEffect去同时修改H,因为A和B获取数据是async的,所以哪一个先到先第一个覆盖数据。

当A先到,B后到时,B就会覆盖A的所有数据。因为H结构不能有空值,但是B数据又没有A数据,所以只能设置默认值,这样设置的默认值会把先前A设置的数据覆盖。

所以需要一个useEffect同时设定A和B。上面的代码块就是一个例子。

相关推荐
NiceCloud喜云8 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
wordbaby9 小时前
React Native + RNOH:跨页面数据回传的最佳实践与避坑指南
前端·react native
丷丩9 小时前
MapLibre GL JS第22课:查看本地GeoJSON
前端·javascript·map·mapbox·maplibre gl js
Front思10 小时前
AI前端工程师需要具备能力+
前端·人工智能·ai
ZC跨境爬虫12 小时前
跟着 MDN 学CSS day_29:(掌握文本与字体样式的核心艺术)
前端·css·ui·html·tensorflow
李子琪。12 小时前
网络空间安全深度实战:CSRF 漏洞原理剖析与基于 Token 的纵深防御体系构建(全栈实验报告)
前端·安全·csrf
冰暮流星13 小时前
javascript之history对象介绍
前端·笔记
IT_陈寒13 小时前
Vite热更新失灵?你可能漏了这个配置
前端·人工智能·后端
丷丩13 小时前
MapLibre GL JS第19课:实时更新要素
前端·javascript·gis·map·mapbox·maplibre gl js
Mr.Daozhi13 小时前
RAG 进阶实战:跑通 Demo 后我连续翻了 6 次车,逐一修复才真正可用(含 Gradio Web 版)
前端·数据库·langchain·大模型·gradio·rag·科研工具