React Hooks - useEffect

1,973 阅读4分钟

让我们来谈谈 React Hooks 中的 useEffect~

  1. 什么是useEffect?
  2. 了解useEffect依赖数组
  3. 了解useEffect清理函数
  4. useEffect中的无限循环是什么?
  5. useEffect 中 async-await 函数的使用

1. 什么是useEffect?

useEffect是一个钩子,可以用来替换一些 React Lifecycle(生命周期)方法。因此,useEffect行为类似于类生命周期方法。useEffect用于在以下情况下对功能组件执行。

  • 渲染组件时(基于类组件中的 componentDidMount
  • 组件更新时(基于类组件中的 componentDidUpdated
  • 当组件从 DOM 中移除时(基于类组件中的 componentWillUnmount

2. 了解useEffect依赖数组

useEffect需要两个参数。第一个参数是一个回调函数,我们将对其执行副作用;第二个参数是依赖数组,是可选的。

如果我们不传递第二个参数,那么每次重新渲染组件时,回调函数中的副作用都会再次运行。

function MyComponent() { 
  useEffect(() => { 
    // 每次渲染后都会运行副作用
  }) 
}

如果我们将第二个参数作为空数组传递,回调函数中的副作用只会在组件第一次渲染时运行一次。

function MyComponent() { 
  useEffect(() => { 
    // 这个副作用只会运行一次,在第一次渲染之后
  }, []) 
}

如果我们在第二个参数中传递了 props 或 state 值,那么回调函数中的副作用只会在 props 或 state 值发生变化时运行。

import { useEffect, useState } from 'react' 

function MyComponent({ prop }) { 
  const [state, setState] = useState('') 
  useEffect(() => { 
    // 副作用只会在 props 或 state 时运行改变
  },[prop,state])
}

注意:useEffect 使用浅比较来比较 useEffect 的依赖值。

3. 了解useEffect清理功能

useEffect中一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!

function MyComponent() { 
  useEffect(() => { 
    // 这个副作用会在每次渲染之后运行
    return () => { 
      // 这个副作用会在组件卸载之前运行,可以在此清除订阅的外部数据源等
    } 
  }) 
}

举个例子

import { useEffect } from "react"

const Modal = ({ modalContent, closeModal }) => {
  useEffect(() => {
    let timeout = setTimeout(() => closeModal(), 3000)

    return () => clearTimeout(timeout)
  })
  
  return (
    <div className="modal">
      <p>{modalContent}</p>
    </div>
  )
}

export default Modal

4、useEffect中的无限循环是什么?

虽然使用useEffect很常见,但要掌握它需要一些时间。这就是为什么许多新用户可以将 设置useEffect为导致无限循环的原因

如果没有指定依赖数组

function App() { 
  const [user, setUser] = useState([]) 
  useEffect(() => { 
    const getUser = async () => { 
      const {data} = await axios.get("/api/user") 
      setUser(data) 
    } 
    getUser() 
  }) // 没有依赖数组
}

渲染组件时,用户的值(状态)会发生变化。因为状态已经改变,所以组件被渲染。因为我们没有指定依赖数组,所以useEffect又在运行,状态又在变化。

解决方法

function App() {
  const [user, setUser] = useState([])
  useEffect(() => {
    const getUser = async () => {
      const {data} = await axios.get("/api/user")
      setUser(data)
    }
    getUser()
  }, []) // 空数组
}

如果在依赖数组中指定了一个函数

function App() {
  const [count, setCount] = useState(0)

  const getResult = () => {
    return 2 * 2
  }

  useEffect(() => {
    setCount((count) => count + 1)
  }, [getResult])

  return (
    <div>
      <p>值: {count}</p>
    </div>
  )
}
export default App

我们知道,数据更新时。执行useEffect浅比较以验证依赖项是否已更新。使用setCount,state在组件第一次渲染时更新。

因为状态更新了,组件又被渲染了。因为getResult是一个函数,所以每次渲染组件时都会重新创建内存中的参考值。因此,浅比较的结果返回 false。这样就形成了无限循环。

解决方案

function App() {
  const [count, setCount] = useState(0)
  
  const getResult = useCallback(() => {
    return 2 * 2
  }, [])

  useEffect(() => {
    setCount((count) => count + 1)
  }, [getResult])

  return (
    <div>
      <p>值: {count}</p>
    </div>
  )
}
export default App

记忆getResult函数。这确保了getResult函数的参考值不会改变。当useEffect进行浅比较时,它会返回 true,并且不会渲染组件。

注意:有很多方法可以避免 Component 中的无限循环,欢迎大家讨论

5. useEffect中Async-Await函数的使用

如果我们想使用 API 获取数据,我们需要执行异步操作。我们怎样才能用useEffect?

  • 在 useEffect 之外创建 async 函数并在 useEffect 中调用它
const getUser = async () => { 
  const { data } = await axios.get('api/user') 
  setUser(data) 
} 

useEffect(() => { 
  getUser() 
}, [])
  • 在useEffect中创建async函数,在useEffect中调用。
useEffect(() => { 
  const getUser = async () => { 
    const { data } = await axios.get('api/user') 
    setUser(data) 
  } 
  getUser() 
}, [])
  • 在useEffect中使用IIFE(立即执行函数)
useEffect(() => { 
  (async () => { 
    const { data } = await axios.get('api/user') 
    setUser(data) 
  })() 
}, [])

结论

useEffect是一个非常强大且广泛使用的 React 钩子,本文只是对useEffect简单讲解了一下,更多详细的内容后续会继续讲解。