useEffect
useEffect 是一个 React Hook,它可以让代码在每次渲染后执行、在组件挂载后执行以及在指定依赖项变化后执行。
使用方式为
useEffect(setup, dependencies?)
传入的参数
- 处理Effect的函数 setup 。setup 函数可以设置返回值,但它的返回值只能是一个函数,成为cleanup函数。当组件被添加到 DOM 的时候,React 将运行 setup 函数。在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除后,React 将最后一次运行 cleanup 函数。
- 可选的依赖项 dependencies 。依赖项 dependencies 为空的时候,setup 函数会在每次渲染的时候执行;当依赖项 dependencies 为空数组的时候,setup 函数会在组件挂载后执行;当依赖项 dependencies 数组里面有变量的时候,setup 函数会在指定依赖项变化后执行。
useEffect 返回值为 undefined 。
注意事项:
- useEffect 是一个 Hook,因此只能在 组件的顶层 或自己的 Hook 中调用它,而不能在循环或者条件内部调用。
- setup 函数中使用的每一个响应值都必须设为依赖项。
Effect
根据 React 文档的解释,Effect 允许你指定由渲染本身,而不是特定事件引起的副作用。
因为 React 组件有两种逻辑:
- 渲染逻辑:主要体现在JSX上。
- 事件处理函数:用户与客户端、移动端或web端交互的事件引起的“副作用”,也就是事件监听的回调函数。
但是有些事情是由渲染引起的,比如类似于vue中的生命周期函数created、onMounted等以及vue的监听器watch,可以在组件挂载、组件渲染以及由变量更新引起的组件视图更新的渲染后执行对应的代码。
所以就需要 Effect 作为渲染事件引起的副作用,去执行对应的业务逻辑。我们可以在这个副作用 Effect 里面去执行一些与组件之外,也就是外部系统的交互。
在组件中使用
import { useState, useEffect, useRef } from "react"
export default function UseEffectDemo () {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('msg')
const [arr, setArr] = useState([])
const countRef = useRef(0)
// 只是首次渲染的时候执行
useEffect(() => {
console.log('useEffect')
}, [])
// 只在变量 count 变化的时候执行
useEffect(() => {
console.log('useEffect-count')
}, [count])
// 只在变量 msg 变化的时候执行
useEffect(() => {
console.log('useEffect-msg')
}, [msg])
// 只在变量 arr 变化的时候执行
useEffect(() => {
console.log('useEffect-arr')
}, [arr])
// 会在变量 count 和 msg 其中一个变化的时候执行
useEffect(() => {
console.log('useEffect-count-msg')
}, [count, msg])
const handleClick = () => {
setCount(count + 1)
}
const handleInput = (e) => {
setMsg(e.target.value)
}
const handleAdd = () => {
countRef.current = countRef.current + 1
setArr([
...arr,
{ id: countRef.current, text: `${countRef.current}. arr`}
])
}
return (
<div>
<div>{msg}-count: {count}</div>
<div>
<button onClick={handleClick}>add</button>
</div>
<div>
<input type="text" onChange={handleInput} />
</div>
<div>
<button onClick={handleAdd}>add arr</button>
</div>
<div>
<ul>
{arr.map(item => {
return <li key={item.id}>{item.text}</li>
})}
</ul>
</div>
</div>
)
}
cleanup 函数
cleanup 函数是 setup 函数的返回值。一般用于清理副作用,处理 setup 函数中遗留问题,比如断开连接,避免再次执行 setup 函数的时候重复连接;清除定时器和倒计时,避免定时器和倒计时占用调用栈等等。
注意事项:
- 在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。
删除不必要的依赖项对象
如果你的 Effect 依赖于在渲染期间创建的对象或函数,则它可能会频繁运行。因为 React 的渲染像生成一张快照,每次渲染函数内部都有独立的 props 、 state 和 变量。如果在渲染函数内部声明了一个对象或函数,那么 React 的渲染函数每次执行的时候,这个对象或函数的引用地址都是不一样的,这就会导致 Object.is
返回 false
。
import { useState, useEffect, useRef } from "react"
export default function UseEffectDemo () {
...
// 避免对象重复执行
const tempObj = {
label: 'apple',
key: 'apple',
count
}
useEffect(() => {
console.log('useEffect-tempObj', tempObj)
}, [tempObj])
const handleClick = () => {
setCount(count + 1)
}
const handleInput = (e) => {
setMsg(e.target.value)
}
const handleAdd = () => {
countRef.current = countRef.current + 1
setArr([
...arr,
{ id: countRef.current, text: `${countRef.current}. arr`}
])
}
return (
<div>
<div>{msg}-count: {count}</div>
<div>
<button onClick={handleClick}>add</button>
</div>
<div>
<input type="text" onChange={handleInput} />
</div>
<div>
<button onClick={handleAdd}>add arr</button>
</div>
<div>
<ul>
{arr.map(item => {
return <li key={item.id}>{item.text}</li>
})}
</ul>
</div>
</div>
)
}
因为这个 tempObj 对象在每次渲染当中都是从头创建的,所以引用地址就不一样。但是我们发现这个 tempObj 对象也依赖于 count 响应值,并且是只依赖于 count 响应值。所以我们可以将 tempObj 对象放在 useEffect 里面创建,就可以把 useEffect 的依赖项改为 count 。
import { useState, useEffect, useRef } from "react"
export default function UseEffectDemo () {
...
// 避免对象重复执行
// const tempObj = {
// label: 'apple',
// key: 'apple',
// count
// }
useEffect(() => {
const tempObj = {
label: 'apple',
key: 'apple',
count
}
console.log('useEffect-tempObj', tempObj)
}, [count])
const handleClick = () => {
setCount(count + 1)
}
const handleInput = (e) => {
setMsg(e.target.value)
}
const handleAdd = () => {
countRef.current = countRef.current + 1
setArr([
...arr,
{ id: countRef.current, text: `${countRef.current}. arr`}
])
}
return (
<div>
<div>{msg}-count: {count}</div>
<div>
<button onClick={handleClick}>add</button>
</div>
<div>
<input type="text" onChange={handleInput} />
</div>
<div>
<button onClick={handleAdd}>add arr</button>
</div>
<div>
<ul>
{arr.map(item => {
return <li key={item.id}>{item.text}</li>
})}
</ul>
</div>
</div>
)
}