【问题标题】:react useEffect comparing objects反应 useEffect 比较对象
【发布时间】: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,但看起来下面有很多很好的答案。

标签: javascript reactjs react-hooks


【解决方案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 :-( stackoverflow.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 { ... };
};

这个想法是只传输效果所依赖的值数组作为第二个参数

【讨论】:

    猜你喜欢
    • 2020-09-25
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-19
    • 2013-11-01
    相关资源
    最近更新 更多