javascript - 与 useEffect 一起使用时,自定义钩子(Hook)会被无限调用

标签 javascript reactjs typescript react-hooks swr

在重构代码以获取帐户信息时,出现了页面无限期重新渲染的问题。

这是我用来获取帐户信息的自定义 Hook 。

//flattenTree returns an array of flattened Object that has children.
const flattenTree = <T extends { children: T[] }>(treeObject: T) => {
  const flattenedTree: T[] = []
  flattenedTree.push(treeObject)
  const queue = [treeObject]
  while (queue.length !== 0) {
    const searching = queue.shift()
    for (const child of searching?.children || []) {
      flattenedTree.push(child)
      queue.push(child)
    }
  }
  return flattenedTree
}


const useFetchAccounts = () => {
  const [accountsA, setAccountsA] = useState([])
  const [accountsB, setAccountsB] = useState([])
  const [accountIdsA, setAccountIdsA] = useState(new Set())
  const [accountIdsB, setAccountIdsB] = useState(new Set())

  const { data: accountTreeA } = useSWR(
    `${FETCH_ACCOUNT_URL}/${ACCOUNTA_ID}`,
    fetchAccount
  )
  const { data: accountTreeB } = useSWR(
    `${FETCH_ACCOUNT_URL}/${ACCOUNTB_ID}`,
    fetchAccount
  )

  useEffect(() => {
    if (accountTreeA) {
      setAccountsA(flattenTree(accountTreeA))
    }
  }, [accountTreeA])
  useEffect(() => {
    if (accountTreeB) {
      setAccountsB(flattenTree(accountTreeB))
    }
  }, [accountTreeB])

  useEffect(() => {
    setAccountIdsA(new Set(accountsA.map((account) => account.accountId)))
  }, [accountsA])
  useEffect(() => {
    setAccountIdsB(new Set(accountsB.map((account) => account.accountId)))
  }, [accountsB])

  return {
    accounts: {
      accountsA,
      accountsB,
    },
    accountIds: {
      accountIdsA,
      accountIdsB,
    },
  }
}

这是使用上面自定义钩子(Hook)的组件。

const TestPage: React.FC = () => {
  const [accountsC, setAccountsC] = useState<Account[]>([])
  const [accountIdsC, setAccountIdsC] = useState<Set<string>>(new Set())
  const [pageC, setPageC] = useState<LongShortPage[]>([])
  const { accounts, accountIds } = useFetchAccounts()

  const { data: pageA } = useSWR(
    {
      url: FETCH_BOOK_BY_ACCOUNT_ID,
      targetAccountIds: [...accountIds.accountIdsA],
    },
    fetchPagesByAccountIds
  )

  useEffect(() => {
    const { accountsA } = accounts
    const { accountIdsA, accountIdsB } = accountIds
    setAccountsC(accountsA.filter((account) => !accountIdsB.has(account.accountId)))
    setAccountIdsC(
      new Set([...accountIdsA].filter((accountId) => !accountIdsB.has(accountId)))
    )
  }, [accounts, accountIds])

  useEffect(() => {
    if (pageA) {
      setPageC(pageA.filter((page) => accountIdsC.has(page.accountId)))
    }
  }, [pageA, accountIdsC])

  return (
    <li>
      {[...accountIdsC].map((id) => (
        <div key={id}>${id}</div>
      ))}
      {/*
      elision
      */}
    </li>
  )
}

似乎 useFetchAccounts 被无限调用,因此我的组件无限渲染。

于是,我以为自定义hook的使用方式有问题,于是将自定义hook内部的所有逻辑都移到了组件中,问题就解决了。

为什么会发生这种情况?

如何修改或重构代码?

这是一个重现该问题的codesandbox示例。

Edit msw-react-example (forked)

最佳答案

您可以使用useMemo来记住这些值。当您使用 {} 返回一个对象时,它将假定该对象已更改到新位置。

return useMemo(
  () => ({
  accounts: {
    accountsA,
    accountsB,
  },
  accountIds: {
    accountIdsA,
    accountIdsB,
  },
}), [accountsA, accountsB, accountIdsA, accountIdsB]);

更新相关评论的答案。

关于javascript - 与 useEffect 一起使用时,自定义钩子(Hook)会被无限调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73982780/

相关文章:

javascript - 从 PHP 获取 Javascript 中的 .src

javascript - React 应用程序必须在新部署后清除浏览器缓存

reactjs - "gatsby-node.js"运行 onCreateNode 生命周期时抛出错误 : fmImagesToRelative is not a function

javascript - 给定值数组时从 json 中提取单个属性

javascript - WinJS 缓存 facebook 个人资料图片

javascript - 将隐藏行的值获取到 JavaScript 中

javascript - Jquery 选择元素

javascript - react : how to filter an array of objects in props

css - 如何使模块 css 在 gatsby 应用程序中与 typescript 一起使用?

node.js - Sequelize 生成错误的查询? Sequelize 自动 ts 和 Sequelize