react useEffect 比较对象

IT技术 javascript reactjs react-hooks
2021-01-18 09:30:44

我正在使用reactuseEffect钩子并检查对象是否已更改,然后才再次运行钩子。

我的代码看起来像这样。

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)有效?

6个回答

使用apiOptions的状态值

我不确定您是如何使用自定义钩子的,但是apiOptions通过使用创建状态值useState应该可以正常工作。通过这种方式,您可以将其作为状态值提供给您的自定义钩子,如下所示:

const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions)

这样它只会在您使用setApiOptions.

示例#1

或者

您可以useEffect按照此处的描述编写一个深度比较

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.

@lenilsondc 对于另一种情况,将数组传递给 useEffect 依赖项很重要-如果传递对象,则它将无法按预期工作。
2021-03-15 09:30:44
@peterflanagan 的蛋头视频(我也没有访问权限)描述了一个useEffect带有深度比较钩子的实现我已经用提示更新了我的答案,希望它有所帮助,但是您必须使用 lodash 或其他方法来实现最适合您的比较算法,但如果您不能使用 3rd 方库,则可以实施你自己的。
2021-03-17 09:30:44
@lenilsondc 在替代方法中,没有传递给 useEffect 的数组。你能解释一下这是如何工作的吗?当我尝试使用这种方法时,ESLint 抱怨我将一个函数作为第二个参数传递给 useEffect 而不是数组。
2021-03-21 09:30:44
感谢您的帮助,但我不想采用这种方法,因为可能有很多 API 选项。我已经通过使用解决了这个问题,JSON.stringify但看起来这里描述了一种更好的方法,但我无法访问 egghead :-( stackoverflow.com/questions/53601931/...
2021-03-27 09:30:44
@peterflanagan 是didComponentUpdate不是切换到基于类的组件会更好JSON.stringify可能会抵消任何性能提升,而复杂的比较可能会抵消功能组件更好的可读性。
2021-03-29 09:30:44

我刚刚找到了一个对我有用的解决方案。

你必须使用usePrevious()_.isEqual()来自 Lodash。在 中useEffect(),如果前一个 apiOptions等于当前 ,则放置一个条件apiOptions如果为真,则什么都不做如果为假updateData()

例子 :

const useExample = (apiOptions) => {

     const myPreviousState = usePrevious(apiOptions);
     const [data, updateData] = useState([]);
     useEffect(() => {
        if (myPreviousState && !_.isEqual(myPreviousState, apiOptions)) {
          updateData(apiOptions);
        }
     }, [apiOptions])
}

usePrevious(value)是一个自定义钩子,它创建一个refwith useRef()

您可以从官方 React Hook 文档 中找到它

const usePrevious = value => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
它是否收到有关缺少依赖项的警告myPreviousState
2021-04-03 09:30:44

如果您真的确定您无法控制,apiOptions那么只需将原生 useEffect 替换为https://github.com/kentcdodds/use-deep-compare-effect

这是正确答案。useDeepCompareEffect也由主工程师背后的阵营测试库开发。此外,代码useDeepCompareEffect非常小且易于理解。我强烈建议阅读它。
2021-04-08 09:30:44

如果输入足够浅,您认为深度相等仍然很快,请考虑使用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
    };
};

请注意,它不会比较功能。

@marcvangend 如果您使用apiOptions来自父作用域的 ,则必须将其添加为 useEffect 的依赖项,这将带来问题..
2021-03-27 09:30:44
apiOptions是一个不太复杂的数据对象时,这似乎是一个很好且简单的解决方案但我想知道:有必要JSON.parse(apiOptionsJsonString)在 useEffect() 里面吗?你不能简单地apiOptions从父作用域使用吗?
2021-03-30 09:30:44

您可以使用 useDeepCompareEffect、useCustomCompareEffect 或编写自己的钩子。

https://github.com/kentcdodds/use-deep-compare-effect https://github.com/sanjagh/use-custom-compare-effect

欢迎来到 SO!我们非常感谢您的帮助和回答问题。也许更好地解释链接中的内容并将其用作参考。
2021-03-18 09:30:44