反应 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 作为状态值

我不确定您是如何使用自定义挂钩的,但使用 useStateapiOptions 设为状态值应该可以正常工作。这样您就可以将其作为状态值提供给您的自定义钩子,如下所示:

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 不可分配

useEffect 在自定义钩子中使用 ref 缺少依赖项警告

深度反应useEffect / useEffect的使用?

反应 useEffect 和 useRef

反应 useEffect 和 setInterval

在函数内反应 useEffect 陈旧值