【发布时间】:2021-11-11 00:09:30
【问题描述】:
我有以下 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 进行深入比较,而不必创建另一个挂钩。
有什么想法吗?
=====================
更新:
由于这种情况,我处于当前情况。也许这可以做得更简单,但说实话,我已经尽力了(我不是超级专业)
- 我有一个包含不同模块的文件夹“api”,其中包含连接到我的服务器的不同功能(我没有使用挂钩 这部分)
services/
firebase/
api/
users/
helpers/
cache/
usersCache.js
index.js
...
如您所见,我有一个模块“usersCache.js”(记忆模式),它将避免一些 RTT 并降低成本。为了避免 RAM 出现问题,我在那里实现了一个缓存算法“LRU”。
- 在我的应用程序中我没有使用 REDUX(可能是我采取的最糟糕的想法,但为时已晚,90% 的工作已经完成。将尝试学习 这项技术并在生产中采用它,并进行长时间的重构)。
为了管理复杂的状态,我使用了 React Context + useReducer,这在某种程度上简化了我的生活,因为它有点类似于 Redux。
就我而言,对于用户,我有 2 个上下文:
- 当前用户上下文
- 其他用户上下文
它们都包含获取的用户的敏感和非敏感数据。这里你可能会想:为什么要有usersCache?如果两个上下文都可以用作内存缓存?
答案(至少我认为)是:
-
没有办法在 React 组件或钩子之外使用上下文。为了返回缓存的数据并避免发出服务器请求,它不可能在我的 api 模块中执行。
-
我将敏感数据保存在依赖于当前用户会话的上下文中(例如 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,
},
...
}
顶级字段不乱。
【问题讨论】:
-
您尝试做的事情有很多可怕和错误的地方。
JSON.stringify(complexNestedObject)是一个巨大的危险信号,表明这里的某些事情已经脱轨。你的目标是什么?你想解决什么问题?不是,“我创建了这些奇怪的不可维护的钩子,我需要修复它们”——而是为什么你首先创建了这些奇怪的不可维护的钩子?老实说,这听起来像是你创造了它们,因为你只是没有理解正确的反应。 -
您可能采取了一条非常困难的道路,这将使您的代码无法维护。 react 提供的 useEffect hook 实际上涵盖了很多你不需要超越的用例。也许,如果您详细说明您需要做的事情,社区会帮助您找到更简单的解决方案。
-
@Adam 我有一个上下文提供程序,它提供了更新有状态用户数据的方法。我正在使用减速器......所以没有办法等待调度,因为它没有返回承诺。所以......因为在纯函数中执行副作用是一种反模式,并且用户数据可能会因为很多情况而改变(拉动刷新,前端动作等)我决定使用(为了避免长长的 useEffects 列表)一个独特的 useDidUpdateEffect,当任何用户数据字段更改时运行 common 副作用。
-
常见的副作用是更新我的内存用户缓存(不依赖于当前用户会话),以便在双方都有最新的数据,我的 LRU 缓存和我当前的用户上下文。
-
@Raul - 我几乎不明白这些。如果您
thunk它绝对可以等待调度,请参阅 medium.com/solute-labs/…。但我仍然不明白它的其余部分。不过,我确实敦促你,无论你在做什么,你都过于复杂了,应该退后一步。也许use-context-selector也可以在这里帮助你
标签: javascript reactjs react-native react-hooks