简体   繁体   English

无限反应自定义钩子渲染

[英]React custom hook rendering infinitely

I made a custom hook that fetches a News API and returns a handler for loading, errors and data (inspired by Apollo Client).我制作了一个自定义钩子,用于获取新闻 API 并返回用于加载、错误和数据的处理程序(受 Apollo Client 启发)。 The problem is that when using it, it will fire itself infinitely, even though the items in the dependency array don't change.问题是当使用它时,它会无限地触发自己,即使依赖数组中的项目没有改变。 This is how I'm implementing it:这就是我实现它的方式:

The hook:钩子:

const useSearch = (query: string, sources: string[]) => {
  const [response, setResponse] = useState<State>({
    data: null,
    loading: true,
    error: null,
  });

  useEffect(() => {
    newsapi
      .getEverything({
        q: query,
        pageSize: 1,
        sources: sources,
      })
      .then((data) => {
        setResponse({ data, loading: false, error: null });
      })
      .catch((e) => {
        setResponse({ data: null, loading: false, error: e });
      });
  }, [query, sources]);

  return response;
};

Usage:用法:

  const { loading, error, data } = useSearch("Donald", ["bbc-news"]);

And I exceeded my daily rate for the API:我超过了 API 的每日费率:

在此处输入图像描述

What am I doing wrong?我究竟做错了什么?

I provided the solution, and @JacobSmit explained in the comment section.我提供了解决方案,@JacobSmit 在评论部分进行了解释。 Now I just organize them into an answer with more details, hope it'd be helpful to latecomer.现在我只是将它们组织成一个更详细的答案,希望对后来者有所帮助。

Solution解决方案

const useSearch = (query: string, sources: string[]) => {
  // ...
  useEffect(() => {
    // ...

    // FIX:
    // just apply the spread operator (...) to `sources`
    // to spread its elements into the dependency array of `useEffect`
  }, [query, ...sources]);

  return response;
};

Explanation解释

The useSearch custom hook passes [query, sources] to the dep array of useEffect , where as sources: string[] is an array itself. useSearch自定义钩子将[query, sources]传递给useEffect的 dep 数组,其中sources: string[]本身就是一个数组。 That makes the dep array of shape:这使得 dep 数组的形状:

["query", ["source_1", "source_2", ..., "source_n"]]

See that the second element of dep array is a nested array.看到 dep 数组的第二个元素是一个嵌套数组。 However, the way useEffect consumes the dep array, is to apply Object.is equality check to each of it's elements:但是, useEffect使用 dep 数组的方式是对每个元素应用Object.is相等性检查:

// pseudo code
function isDepArrayEqual(prevDepArray: any[], currDepArray: any[]) {
  return prevDepArray.every(
    (prevElement, index) => Object.is(prevElement, currDepArray[index])
  )
}

With each re-render, the hook call useSearch("Donald", ["bbc-news"]) creates a new instance of sources array.每次重新渲染时,钩子调用useSearch("Donald", ["bbc-news"])创建一个新的sources数组实例。 That'll fail the Object.is(prevSources, currSources) check, since equality of arrays is compared by their reference, not the value(s) they contain.Object.is(prevSources, currSources)检查失败,因为 arrays 的相等性是通过它们的引用而不是它们包含的值进行比较的。

With the spread operator [query, ...sources] , you transform the shape of dep array into:使用扩展运算符[query, ...sources] ,您可以将 dep 数组的形状转换为:

["query", "source_1", "source_2", ..., "source_n"]

The key difference is not about copying , but unpacking the sources array.关键的区别不是复制,而是解包sources数组。

Now that the nested sources array is unpacked, and each element of dep array is just string.现在嵌套的sources数组已解包,并且 dep 数组的每个元素都只是字符串。 A equality check on strings is compared by their value, not reference, thus useEffect will consider dep array unchanged.对字符串的相等性检查是通过它们的值而不是引用来比较的,因此useEffect将认为 dep 数组没有改变。 Bug fixed.错误已修复。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM