让我们来谈谈 React Hooks 中的 useEffect~
- 什么是useEffect?
- 了解useEffect依赖数组
- 了解useEffect清理函数
- useEffect中的无限循环是什么?
- 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简单讲解了一下,更多详细的内容后续会继续讲解。