useEffect
react的函数组件里面没有生命周期,也没有state,没有state 可以用 useState
来替代。
useEffect
可以看作是componentDidMount
、componentDidUpdate
、componentWillUnmounrt
三个生命周期的组合,可以覆盖这些生命周期函数组合起来使用的所有场景。useEffect
是定义副作用的,可以在函数组件中执行一些副作用操作。
执行时机
useEffect
可以看做三个生命周期函数的组合,也就是在以下时候会执行
componentDidMount
(组件已经挂载)
componentDidUpdate
(组件已经更新)
componentWillUnmount
(组件即将卸载)
副作用
函数的副作用就是函数中除了操作函数内部的变量执行业务还操作了函数外部变量来执行业务。
react项目中,一般副作用操作有:
- 发送ajax请求
- 调用浏览器的API(比如启动定时器、浏览器的存储信息
localStorage.getItem()
等) - 手动更改真实DOM
useEffect使用
useEffect()
有两个参数:第一个参数是要执行的回调函数也就是需要执行的操作,它是必填参数,第二个参数是一个依赖项数组,根据数组里的变量是否变化决定是否执行函数(根据需求第二个参数决定是否填写,它是可选的)
js
import {useEffect} from "react"
useEffect(effect,dependencies)
useEffect
根据第二个参数的传参有不同的作用:
- 不传第二个参数相当于
componentDidMount
和componentDidUpdate
- 第二个参数传空数组相当于
componentDidMount
- 第二个参数数组中存放某个变量相当于
watch
监听
useEffect
的第一个参数Effect
是要执行的副作用函数,它可以是任意的自定义函数,可以在这个函数里面操作一些浏览器的API或者和外部环境进行交互比如网络请求等。这个函数会在每次组件渲染完成之后被调用,组件每渲染一次该函数就自动执行一次。在组件的页面首次DOM 加载后,副作用函数也会执行。
useEffect
的第二个参数dependencies
用于限制该副作用的执行条件,使用一个数组指定副作用函数的依赖项,只有依赖项发生变化,才会重新渲染。如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副作用函数这时只会在组件首次加载完成后执行一次,后面组件重新渲染,就不会再次执行。
不传第二个参数举例说明
js
import { useEffect,useState } from 'react'
function App() {
let [count,setCount] = useState(0);
const addHandle = () => {
let num = count++;
setCount(num);
};
// 第一个参数是一个回调函数,必填项
useEffect(() => {
console.log("effect")
});
return (
<>
<h1>App</h1>
<p>{count}</p>
<button onClick={addHandle}>add</button>
</>
)
}
export default App
控制台第一次输出表示在组件挂载后执行了
useEffect
的回调函数,相当于componentDidMount
的作用。之后点击按钮修改了数据,在组件更新后useEffect
的回调函数再次触发,相当于componentDidUpdate
的作用。
第二个参数传入空数组举例说明
js
import { useEffect,useState } from 'react'
function App() {
let [count,setCount] = useState(0);
const addHandle = () => {
let num = count++;
setCount(num);
};
// 第一个参数是一个回调函数,必填项
useEffect(() => {
console.log("effect")
},[]);
return (
<>
<h1>App</h1>
<p>{count}</p>
<button onClick={addHandle}>add</button>
</>
)
}
export default App
控制台第一次输出表示在组件挂载后执行了
useEffect
的回调函数,相当于componentDidMount
的作用。之后点击按钮修改了数据,在组件更新后useEffect
的回调函数没有再次执行。
js
import {useState,useEffect} from 'react'
export default function App() {
let [objarr,setObjarr]=useState([]);
useEffect(()=>{
console.log('App组件对应的模板在页面中加载完成时调用');
fetch('http://127.0.0.1:7001/test')
.then(res=>res.json())
.then(data=>{
console.log(data,111);
setObjarr(data)
});
});
return (
<div>
{
objarr.map((el,index)=>{
return(
<div key={index}>
<p>{el.title}</p>
<p>{el.author}</p>
</div>
)
})
}
<p>App组件</p>
</div>
)
}
在组件第一次加载时网络请求,没有设置依赖项为空数组,就会一直发起网络请求。
setObjarr(data)
每一次都会得到一个新的引用数据然后刷新页面,就会一直发起网络请求,如果后端发送的数据每次都是不同的就会一直刷新页面就容易造成项目进入死循环。
所以需要设置第二个参数是一个空数组,这样副作用函数只会在组件首次加载后执行一次,后面组件重新渲染,就不会再次执行
js
import {useState,useEffect} from 'react'
export default function App() {
let [objarr,setObjarr]=useState([]);
useEffect(()=>{
console.log('App组件对应的模板在页面中加载完成时调用');
fetch('http://127.0.0.1:7001/test')
.then(res=>res.json())
.then(data=>{
console.log(data,111);
setObjarr(data)
});
},[]);
return (
<div>
{
objarr.map((el,index)=>{
return(
<div key={index}>
<p>{el.title}</p>
<p>{el.author}</p>
</div>
)
})
}
<p>App组件</p>
</div>
)
}
第二个参数数组中存放state数据
js
import { useEffect,useState } from 'react'
function App() {
let [count,setCount] = useState(0);
let [msg,setMsg] = useState("hello");
const addCount = () => {
setCount(count++);
};
const changeHandle = () => {
setMsg("hello world");
};
// 第一个参数是一个回调函数,必填项
useEffect(() => {
console.log("effect")
},[msg]);
return (
<>
<h1>App</h1>
<p>{msg}</p>
<p>{count}</p>
<button onClick={addCount}>add</button>
<button onClick={changeHandle}>change</button>
</>
)
}
export default App
控制台第一次输出表示在组件挂载后执行了
useEffect
的回调函数,相当于componentDidMount
的作用。之后修改了监听的state数据,在组件更新后useEffect
的回调函数再次执行。而修改了其他数据,此时useEffect
的回调函数也不会执行。
此时和vue的watch监听是有区别的,vue的watch监听默认是一开始不会执行回调函数的,而使用useEffect
来监听某个数据,开始就会执行一次回调函数,类似于componentDidMount
的作用。
当然数组里面也可以写多个([数据1,数据2]
),同时监听多个数据的变化。
js
import { useEffect,useState } from 'react'
function App() {
let [count,setCount] = useState(0);
let [msg,setMsg] = useState("hello");
const addCount = () => {
setCount(count++);
};
const changeHandle = () => {
setMsg("hello world");
};
// 第一个参数是一个回调函数,必填项
useEffect(() => {
console.log("effect");
console.log(count,msg)
},[msg,count]);
return (
<>
<h1>App</h1>
<p>{msg}</p>
<p>{count}</p>
<button onClick={addCount}>add</button>
<button onClick={changeHandle}>change</button>
</>
)
}
export default App
清除页面的副作用
副作用是随着组件加载而发生的,那么组件卸载时,可能需要清理这些副效应。 useEffect()
允许返回一个函数,在组件卸载时清理副作用。如果不需要清理副作用,useEffect()
就不用返回任何值。
js
import { useEffect } from 'react'
export default function Des() {
useEffect(() => {
setInterval(() => {
console.log('Des组件对应的模板在页面中加载完成时调用');
}, 1000);
});
return (
<div>Des组件</div>
)
}
js
import {useState} from 'react'
import Des from './Des.jsx'
export default function App() {
let [flag,setFlag]=useState(true);
let change=()=>{
setFlag(false)
}
return (
<div>
<p>App组件</p>
{flag&&<Des></Des>}
{/* flag为true时,会执行后面的表达式,逻辑与表达式的值就是后面表达式的返回值显示Des组件 */}
{/* flag为false时,不会执行后面的表达式,不会显示Des组件 */}
<button onClick={change}>销毁Des组件</button>
</div>
)
}
Des组件被销毁了,但是组件中的副作用依然存在,所以需要在组件卸载时清理副作用。
js
import { useEffect } from 'react'
export default function Des() {
useEffect(() => {
let timer=setInterval(() => {
console.log('Des组件对应的模板在页面中加载完成时调用');
}, 1000);
return ()=>{
clearInterval(timer);
}
});
return (
<div>Des组件</div>
)
}
js
import {useState} from 'react'
import Des from './Des.jsx'
export default function App() {
let [flag,setFlag]=useState(true);
let change=()=>{
setFlag(false)
}
return (
<div>
<p>App组件</p>
{flag&&<Des></Des>}
{/* flag为true时,会执行后面的表达式,逻辑与表达式的值就是后面表达式的返回值显示Des组件 */}
{/* flag为false时,不会执行后面的表达式,不会显示Des组件 */}
<button onClick={change}>销毁Des组件</button>
</div>
)
}
Des组件在销毁时,返回的函数被执行了,组件中的副作用就被清除了。
总结
useEffect(() => { })
:第一个参数回调函数每次组件渲染都执行useEffect(() => { return () => { } }, [ ])
:第一个参数回调函数只在组件首次渲染执行。当组件移除时触发清除副作用的函数,即执行return
的函数useEffect(() => { return () => { } }, [count])
:第一个参数回调函数在组件首次渲染执行以及count
改变才会执行。当组件移除时触发清除副作用的函数。这里的依赖项可以填任何数据类型,只要依赖项发生变化才会重新运行第一个参数回调函数。- 一个函数组件中可以使用多个副作用函数,尽量不同的业务处理写在不同
useEffect
里面。