反应自定义挂钩问题-无限依赖循环

时间:2019-08-31 15:09:47

标签: javascript reactjs react-hooks

我一直很喜欢陷入困境,并处理与现实问题相关的所有新的有趣问题:)这是我遇到过几次,很想看看你的“应该”解决!

概述:我创建了一个自定义钩子,以封装应用程序的某些业务逻辑并存储一些状态。我在组件内部使用了该自定义钩子,并在加载时触发了一个事件。

问题是:我的钩子的loadItems函数需要访问我的items才能获取最后一个项目的ID。将items添加到我的依赖项数组会导致无限循环。这是一个(简化的)示例:

简单的ItemList组件

//
// Simple functional component
//

import React, { useEffect } from 'react'
import useItems from '/path/to/custom/hooks/useItems'

const ItemList = () => {
    const { items, loadItems } = useItems()

    // On load, use our custom hook to fire off an API call
    // NOTE: This is where the problem lies. Since in our hook (below)
    // we rely on `items` to set some params for our API, when items changes
    // `loadItems` will also change, firing off this `useEffect` call again.. and again :)
    useEffect(() => {
        loadItems()
    }, [loadItems])

    return (
        <ul>
            {items.map(item => <li>{item.text}</li>)}
        </ul>
    )
}

export default ItemList

自定义useItems挂钩

//
// Simple custom hook
//

import { useState, useCallback } from 'react'

const useItems = () => {
    const [items, setItems] = useState([])

    // NOTE: Part two of where the problem comes into play. Since I'm using `items`
    // to grab the last item's id, I need to supply that as a dependency to the `loadItems`
    // call per linting (and React docs) instructions. But of course, I'm setting items in
    // this... so every time this is run it will also update.
    const loadItems = useCallback(() => {
        // Grab our last item
        const lastItem = items[items.length - 1]
        // Supply that item's id to our API so we can paginate
        const params = {
            itemsAfter: lastItem ? lastItem.id : nil
        }
        // Now hit our API and update our items
        return Api.fetchItems(params).then(response => setItems(response.data))
    }, [items])

    return { items, loadItems }
}

export default useItems

代码中的注释应该指出问题所在,但是我现在想出的使短毛猫高兴的唯一解决方案是向loadItems调用(例如loadItems({ itemsAfter: ... }))提供参数。因为数据已经在这个自定义钩子中了,所以我真的希望不必在使用loadItems函数的任何地方都要做。

非常感谢您的帮助!

迈克

2 个答案:

答案 0 :(得分:1)

如果您打算只运行一次效果,请忽略所有依赖项:

 useEffect(() => {
    loadItems();
 }, []);

答案 1 :(得分:0)

您可以尝试使用useReducer,将分发作为loadItems传递,因为它永远不会更改引用。减速器只关心操作是否为NONE,因为那是useEffect的清理功能所做的清理。

如果动作不是NONE,则状态将被设置为最后一个项目,这将触发useEffect使用您的api进行获取,当解析后它将使用setItems来设置项目。

const NONE = {};
const useItems = () => {
  const [items, setItems] = useState([]);
  const [lastItem, dispatch] = useReducer(
    (state, action) => {
      return action === NONE
        ? NONE
        : items[items.length - 1];
    },
    NONE
  );

  useEffect(() => {
    //initial useEffect or after cleanup, do nothing
    if (lastItem === NONE) {
      return;
    }
    const params = {
      itemsAfter: lastItem ? lastItem.id : Nil,
    };
    // Now hit our API and update our items
    Api.fetchItems(params).then(response =>
      setItems(response)
    );
    return () => dispatch(NONE); //clean up
  }, [lastItem]);
  //return dispatch as load items, it'll set lastItem and trigger
  // the useEffect
  return { items, loadItems: dispatch };
};