关于 useEffect 重复调用以及依赖数组的问题?

关于useEffect有有一些疑惑疑惑, 如下, 代码已经放入codesandbox 的 CounterHook.js 文件中

代码简化如下:

const CounterHook = props => {
  const [count, setCount] = useState(0)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    setLoading(true)
    console.log(loading) // false, true, false, ture...
    // 模拟发送数据
    setTimeout(() => {
      setLoading(false)
      setCount(1)
    }, 2000)
  })
  
  return ...
}

这里如果没有添加useEffect的依赖数组, 会造成无限循环. 不是特别理解为什么会造成无限循环. 个人打印了一下 loading, 发现一直不断重复输出 false, true, false, true....

第一个问题

个人对造成无限循环的理解:

  • 在调用useEffect之前, loading 是 false
  • 第一次调用useEffect:

    • 类似componentDidMount, 此时 loading 被设置成了 true, 同时发出了一个异步请求
    • 请求结束, loading 又被设置成了 false, 组件的 state 被更新了, 由于 useEffect 在组件更新更新时也会触发, 因此触发第二次useEffect:
  • 第二次调用useEffect: 和第一次一样, 首先componentDidMount, 设置 loading 为 true...循环继续

所以第一个问题是, 不知道这样理解造成无限循环对不对?

第二个问题:

如果将请求数据部分抽成一个函数, 如下;

const CounterHook = props => {
  const [count, setCount] = useState(0)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    fetchApi()
  }, [fetchApi])

  const fetchApi = () => {
    setLoading(true)
    // 模拟发送数据
    setTimeout(() => {
      setLoading(false)
      setCount(1)
    }, 2000)
  }

  return ...
}

useEffect的依赖数组变成了一个函数, 而且此时似乎仍旧会造成无限循环. 同时有提示:

The 'fetchApi' function makes the dependencies of useEffect Hook (at line 27) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'fetchApi' definition into its own useCallback() Hook. (react-hooks/exhaustive-deps)

所以第二个问题为: 如果useEffect的依赖数组存在函数, 该如何理解这个函数是否"变化"? 例如上面例子里面, 为什么会造成无限循环, 函数到底是哪里"变化"了导致useEffect不断的被重新调用?

第三个问题

和第二个问题有关联, 按照提示修改, 使用useCallback包装了一下fetchApi这个函数, 代码如下:

const CounterHook = props => {
  const [count, setCount] = useState(0)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    fetchApiCallback()
  }, [fetchApiCallback])

  const fetchApiCallback = useCallback(() => {
    setLoading(true)
    // 模拟发送数据
    setTimeout(() => {
      setLoading(false)
      setCount(1)
    }, 2000)
  }, [])

  return ...

问题: useCallback依赖添加了一个依赖数组, 里面为空, 似乎代码没有出现无限循环的情况, 请问该如何解释? useCallback到底使得函数的"变化发生了怎样的改变"?

以上是三个问题, 问题有些长与啰嗦, 打扰了各位前辈了. 如果能解答, 真的不胜感激, 谢谢!

阅读 16.7k
2 个回答

第一个问题:
对。

第二个问题:
函数组件的每次渲染都是执行一遍这个函数,在你的例子中,也就是 CounterHook 函数。那么两次执行中创建的局部函数,还会是同一个引用吗?不会的。所以本次执行中的 fetchApi 和上次执行中的 fetchApi 是两个不同的函数,因此 useEffect 的依赖被更新了,触发执行。

第三个问题:
useCallback 的返回值即为它接收的第一个参数,直到依赖更新。因此使用 useCallback 包裹的 fetchApi ,在依赖更新之前,总是同一个函数。也就不会触发 useEffect

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏