首页 > 解决方案 > React Hooks - useDidUpdateEffect 与深度比较

问题描述

我有以下 useDidUpdateEffect 钩子

import { useRef, useEffect } from "react";

export default function useDidUpdateEffect(effect, deps) {
  const didMount = useRef(false);

  useEffect(() => {
    if (didMount.current) {
      effect();
    } else {
      didMount.current = true;
    }
  }, deps);
}

我目前使用它如下:

useDidUpdateEffect(() => { ... }, [JSON.stringify(complexNestedObject)]);

问题是,由于我的 dep 复杂而深入,有时会在没有必要的情况下执行回调。

我有一个 useEffect 解决方案,即 useDeepEffect:

import { useRef, useEffect } from "react";
import { isEqual } from "lodash";

export default function useDeepEffect(effect, deps = undefined) {
  const isFirst = useRef(true);
  const prevDeps = useRef(deps);

  useEffect(() => {
    const isSame = prevDeps.current.every((obj, index) =>
      isEqual(obj, deps[index])
    );

    if (isFirst.current || !isSame) {
      effect();
    }

    isFirst.current = false;
    prevDeps.current = deps;
  }, deps);
}

但是我想不出任何可以与 useDidUpdateEffect 进行深入比较的方法,而不必创建另一个钩子。

有任何想法吗?

=====================

更新:

由于这种情况,我处于目前的情况。也许这可以做得更简单,但说实话,我尽力了(我不是超级专业)

  1. 我有一个包含不同模块的文件夹“api”,其中包含连接到我的服务器的不同功能(这部分我没有使用钩子)
   services/
       firebase/
           api/
              users/
                 helpers/
                 cache/
                    usersCache.js
                 index.js
                 ...

如您所见,我有一个模块“usersCache.js”(记忆模式),它将避免一些 RTT 并降低成本。为了避免 RAM 出现问题,我在那里实现了一个缓存算法“LRU”。

  1. 在我的应用程序中,我没有使用 REDUX(可能是我采用的最糟糕的想法,但为时已晚,90% 的工作已经完成。将尝试学习这项技术并在生产中采用它,并进行长时间的重构)。

为了管理复杂的状态,我使用了 React Context + useReducer,这在某种程度上简化了我的生活,因为它有点类似于 Redux。

就我而言,对于用户,我有 2 个上下文:

  1. 当前用户上下文
  2. 其他用户上下文

它们都包含获取的用户的敏感和非敏感数据。这里你可能会想:为什么要有usersCache?如果两个上下文都可以用作内存缓存?

答案(至少我认为)是:

  1. 没有办法在 React 组件或钩子之外使用上下文。为了返回缓存的数据并避免发出服务器请求,不可能在我的 api 模块中执行此操作。

  2. 我将敏感数据保存在依赖于当前用户会话的上下文中(例如 isFollowed)。因此,当用户注销时,上下文将被卸载(受保护的路由)。另一方面,我的 usersCache 模块仍然存在,没有敏感数据。

这是我的 CurrentUserContext 的一个示例(因为简单,我在这里没有使用减速器,但是在我的 OtherUsersContext 中,因为状态管理很复杂,所以我这样做了):

import React, { createContext } from "react";

import useDidUpdateEffect from "../../hooks/useDidUpdateEffect";
import useStateWithCallback from "../../hooks/useStateWithCallback";
import { usersCache } from "../../services/firebase/api/users";

const CurrentUserContext = createContext(null);

export default CurrentUserContext;

export function CurrentUserProvider({ children }) {
  const [data, setData] = useStateWithCallback(undefined);

  const updateData = (data, merge = true, callback = undefined) => {
    if (merge) {
      setData((prevData) => ({ ...prevData, ...data }), callback);
    } else {
      setData(data, callback);
    }
  };

  useDidUpdateEffect(() => {
    console.log(JSON.stringify(data, null, 2));
    /*
      As the current user data is not sensitive, we can
      synchronize the users cache here.
    */
    usersCache.updateCachedUser(data.id, data);
  }, [JSON.stringify(data)]); // TODO - Avoid unnecessary executions -> deepCompare ?

  return (
    <CurrentUserContext.Provider
      value={{
        data,
        setData,
        updateData,
      }}
    >
      {children}
    </CurrentUserContext.Provider>
  );
}

为了避免运行多个 useDidUpdateEffects,我正在对用户数据进行字符串化。但是,由于它复杂且嵌套:

 userData = {
     avatar: {
         uri,
         thumbnailUri, 
     },
     ...
 }

在数据没有变化的情况下执行效果,因为接收到相同的数据是无序的:

userData = {
     avatar: {
         thumbnailUri,
         uri, 
     },
     ...
 }

顶级字段也不是无序的。

标签: javascriptreactjsreact-nativereact-hooks

解决方案


我认为(我可能是错的)如果你使用这个效果,整个问题就会消失:

useEffect(() => {
  if(!data) return;

  // do some stuff 
},[data]);

推荐阅读