反应 useEffect 比较对象
Posted
技术标签:
【中文标题】反应 useEffect 比较对象【英文标题】:react useEffect comparing objects 【发布时间】:2019-06-03 09:26:17 【问题描述】:我正在使用 react useEffect
钩子并检查对象是否已更改,然后才再次运行钩子。
我的代码如下所示。
const useExample = (apiOptions) =>
const [data, updateData] = useState([]);
useEffect(() =>
const [data, updateData] = useState<any>([]);
doSomethingCool(apiOptions).then(res =>
updateData(response.data);
)
, [apiOptions]);
return
data
;
;
不幸的是,由于对象未被识别为相同,它一直在运行。
我相信下面是一个例子。
const objA =
method: 'GET'
const objB =
method: 'GET'
console.log(objA === objB)
也许运行JSON.stringify(apiOptions)
有效?
【问题讨论】:
有解决方案吗?你没有选择答案,所以我想知道你是不是走了另一条路? @FabricioG 不记得 tbh,但看起来下面有很多很好的答案。 【参考方案1】:使用apiOptions
作为状态值
我不确定您是如何使用自定义挂钩的,但使用 useState
将 apiOptions
设为状态值应该可以正常工作。这样您就可以将其作为状态值提供给您的自定义钩子,如下所示:
const [apiOptions, setApiOptions] = useState( a: 1 )
const data = useExample(apiOptions)
这种方式只有在你使用setApiOptions
时才会改变。
示例 #1
import useState, useEffect from 'react';
const useExample = (apiOptions) =>
const [data, updateData] = useState([]);
useEffect(() =>
console.log('effect triggered')
, [apiOptions]);
return
data
;
export default function App()
const [apiOptions, setApiOptions] = useState( a: 1 )
const data = useExample(apiOptions);
const [somethingElse, setSomethingElse] = useState('default state')
return <div>
<button onClick=() => setApiOptions( a: 1 ) >change apiOptions</button>
<button onClick=() => setSomethingElse('state') >
change something else to force rerender
</button>
</div>;
或者
您可以编写一个深度可比较的useEffect
,如here 所述:
function deepCompareEquals(a, b)
// TODO: implement deep comparison here
// something like lodash
// return _.isEqual(a, b);
function useDeepCompareMemoize(value)
const ref = useRef()
// it can be done by using useMemo as well
// but useRef is rather cleaner and easier
if (!deepCompareEquals(value, ref.current))
ref.current = value
return ref.current
function useDeepCompareEffect(callback, dependencies)
useEffect(
callback,
dependencies.map(useDeepCompareMemoize)
)
您可以像使用 useEffect
一样使用它。
【讨论】:
感谢您的帮助,但我不想采用这种方法,因为可能有很多 API 选项。我已经通过使用JSON.stringify
解决了这个问题,但看起来这里描述了一种更好的方法,但我无法访问egghead :-( ***.com/questions/53601931/…
@peterflanagan 的蛋头视频(我也无权访问)描述了具有深度比较的 useEffect
钩子的实现。我已经用提示更新了我的答案,希望对您有所帮助,但是您必须使用 lodash 或其他方法来实现最适合您的比较算法,但如果您不能使用 3rd 方库,您可以实现你自己的。
@peterflanagan 使用didComponentUpdate
切换到基于类的组件会更好吗? JSON.stringify
可能会抵消任何性能提升,而复杂的比较可能会抵消功能组件更好的可读性。
@lenilsondc 对于另一种情况,将数组传递给 useEffect 依赖项很重要 - 如果您传递一个对象,那么它将无法按预期工作。
@lenilsondc 在替代方法中,没有传递给 useEffect 的数组。你能解释一下这是如何工作的吗?当我尝试使用这种方法时,ESLint 抱怨我将函数作为第二个参数传递给 useEffect 而不是数组。【参考方案2】:
我刚刚找到了一个适合我的解决方案。
您必须使用来自 Lodash 的 usePrevious()
和 _.isEqual()
。
在useEffect()
中,如果previous apiOptions
等于current apiOptions
,则放置一个条件。如果为真,什么都不做。如果为假 updateData().
例子:
const useExample = (apiOptions) =>
const myPreviousState = usePrevious(apiOptions);
const [data, updateData] = useState([]);
useEffect(() =>
if (myPreviousState && !_.isEqual(myPreviousState, apiOptions))
updateData(apiOptions);
, [apiOptions])
usePrevious(value)
是一个自定义钩子,它使用useRef()
创建一个ref
。
你可以从Official React Hook documentation找到它。
const usePrevious = value =>
const ref = useRef();
useEffect(() =>
ref.current = value;
);
return ref.current;
;
【讨论】:
是否收到关于缺少依赖项myPreviousState
的警告?【参考方案3】:
如果输入足够浅,以至于您认为深度相等仍然很快,请考虑使用JSON.stringify
:
const useExample = (apiOptions) =>
const [data, updateData] = useState([]);
const apiOptionsJsonString = JSON.stringify(apiOptions);
useEffect(() =>
const apiOptionsObject = JSON.parse(apiOptionsJsonString);
doSomethingCool(apiOptionsObject).then(response =>
updateData(response.data);
)
, [apiOptionsJsonString]);
return
data
;
;
注意它不会比较函数。
【讨论】:
当apiOptions
是一个不太复杂的数据对象时,这似乎是一个不错且简单的解决方案。但我想知道:是否有必要在 useEffect() 中执行JSON.parse(apiOptionsJsonString)
?您不能简单地使用父作用域中的apiOptions
吗?
@marcvangend 如果您使用父作用域中的apiOptions
,则必须将其作为依赖项添加到 useEffect 中,这将带来问题..【参考方案4】:
如果您确定无法控制apiOptions
,那么只需将本机useEffect 替换为https://github.com/kentcdodds/use-deep-compare-effect。
【讨论】:
这是正确答案。useDeepCompareEffect
也是由 React 测试库背后的主要工程师开发的。此外,useDeepCompareEffect
中的代码非常小且易于理解。我强烈建议阅读它。【参考方案5】:
您可以使用 useDeepCompareEffect、useCustomCompareEffect 或编写自己的钩子。
https://github.com/kentcdodds/use-deep-compare-effect https://github.com/sanjagh/use-custom-compare-effect
【讨论】:
欢迎来到 SO!我们非常感谢您的帮助和回答问题。也许最好解释一下链接中的内容并将其用作参考。【参考方案6】:假设您的apiOptions
的很大一部分是静态的,并且在组件重新渲染时不会改变,我建议采用以下方法:
const useExample = (apiOptions, deps) =>
useEffect(callback, deps);
return ... ;
;
这个想法是只传输效果所依赖的值数组作为第二个参数
【讨论】:
以上是关于反应 useEffect 比较对象的主要内容,如果未能解决你的问题,请参考以下文章
反应 useRef / useEffect HTMLCanvasElement 不可分配