一、 useEffect不加依赖项---每次render都会进行执行调用
上一篇简单介绍了hooks中的useState用法,这一篇继续掰扯掰扯useEffect🤓🤓🤓 从这里开始就要引入一个 capture value概念,这是个大坑。我之前刚入hooks的时候没少遇到。针对这个概念各位看官不必着急,先会用,这个capture value我后面会找个小demo来好好讨论下。
函数组件能保存状态(用useState),但是对于异步请求,副作用的操作还是无能为力,所以 React 提供了 useEffect 来帮助开发者处理函数组件的副作用。在用Class制作组件时,经常会用生命周期函数,来处理一些额外的事情(副作用:和函数业务主逻辑关联不大,特定时间或事件中执行的动作,比如Ajax请求后端数据 , 添加登录监听和取消登录 ,手动修改 DOM等等)。在React Hooks中也需要这样类似的生命周期函数,比如在每次状态(State)更新时执行,它为我们准备了useEffect。
注意: useEffect 在执行时是有顺序的!!! 如果useEffect 不传第二个参数(不传依赖项) ,不仅 初次渲染 会执行,并且只要设置的 任一 state 值改变 都会 触发 useEffect执行!!! 也就是 React初次渲染和之后的每次更新渲染都会调用一遍useEffect函数
1. 例子,用类组件和函数组件来处理副作用:
import React, { Component } from "react";
class App extends Component {
state = {
count: 1
};
componentDidMount() {
const { count } = this.state;
document.title = "componentDidMount" + count;
this.timer = setInterval(() => {
this.setState(({ count }) => ({
count: count + 1
}));
}, 1000);
}
componentDidUpdate() {
const { count } = this.state;
document.title = "componentDidMount" + count;
}
componentWillUnmount(){
clearInterval(this.timer)
document.title = "componentWillUnmount" + count;
}
}
在例子中,组件每隔一秒更新组件状态,并且每次触发更新都会触发 document.title 的更新(副作用),而在组件卸载时修改 document.title(类似于清除)
从例子中可以看到,一些重复的功能开发者需要在 componentDidMount 和 componentDidUpdate 重复编写,而如果使用 useEffect 则完全不一样。
import React, { useState, useEffect } from "react";
let timer = null;
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = "componentDidMount" + count;
},[count]);
useEffect(() => {
timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// return 一个箭头函数进行清除定时器
return () => {
document.title = "componentWillUnmount";
clearInterval(timer);
};
}, []);
return (
<div>
Count: {count}
<button onClick={() => clearInterval(timer)}>clear</button>
</div>
);
}
我们使用 useEffect 重写了上面的例子,useEffect 第一个参数传递函数,可以用来做一些副作用比如异步请求,修改外部参数等行为,而第二个参数是个数组,如果数组中的值改变才会触发 useEffect 第一个参数中的函数,分两种情况: 1 依赖项是 [ ] 空数组,表示在初次渲染和卸载的时候执行; 2 有依赖项,在依赖项发生改变时,useEffect会重新执行。如果有return返回值(如果有)即存在清理函数,他是在每一次运行副作用函数之前运行则在组件销毁或者调用函数前调用。
二、 重要!!!对 useEffect ( fn , [] ) 的解析:
1. 表示在初次render时 执行 getData 这个回调函数 , 后续props改变或其他state的变化都不会引起这个useEffect的执行
1. 不带参数的调用
const getData=()=>{
console.log(11111);
}
useEffect(getData,[])
// 上面代码表示在初次渲染时 执行 getData 这个回调函数
2. 下面代码表示在初次渲染时调用 getData 回调函数并传入参数。
2. 带参数的调用
const getData=(params)=>{
console.log(params);
}
useEffect(()=>{getData(222)},[])
// 上面代码表示在初次渲染时调用 getData 回调函数并传入参数
3. 重点!!利用useEffect 发送 ajax 请求
注意,不能直接在useEffect中使用async,控制台报错中也给出了解决方式,就是把async放在useEffect里面,
useEffect(() => {
const fetchData = async () => {
const result = await axios('XXXXXXXX');
setData(result.data);
}
fetchData();
console.log('执行了')
},[]);
==========================================
或者直解把 这个调用接口的请求方法放在外面
const fetchData = async () => {
const result = await axios('XXXXXXXX');
setData(result.data);
}
useEffect(()=>{
fetchData()
console.log('执行了')// 这里请求方法是带括号的
},[])
三、 清理函数的理解
对下图清理函数执行时间点的分析: 在首次挂载渲染时不执行清理函数,而后其执行的时间点就是在每一次运行副作用函数之前进行执行。注意,组件销毁也会调用清理函数
为防止内存泄漏,清除函数会在组件卸载前执行;如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除,即先执行上一个 effect 中 return 的函数,然后再执行本 effect 中非 return 的函数。
对清理函数的进一步理解,开启消除定时器:
重点: 这涉及到闭包问题: 之前我在项目中遇到了这种问题,在初次请求拿的一直是默认值,不是请求的控制中心开关按钮的状态值!!!!使用useEffect获取更新过的变量值!!
- 在上面第一个例子中对 useEffect 依赖项中的[count]理解起来就是一旦 count 值发生改变,则修改 documen.title 值
- 而第二个 useEffect 中数组没有传值,代表不监听任何参数变化,即只有在组件初始化或销毁的时候才会触发,用来代替 componentDidMount 和 componentWillUnmount
- 在useEffect里return出的是一个函数,即是清理函数,他是在每一次运行副作用函数之前运行即组件销毁或者调用函数前调用
基于这个强大 Hooks,我们可以模拟封装出其他生命周期函数,比如 componentDidUpdate 代码十分简单:
- useEffect的第二个参数为一个空数组,初始化调用一次之后不再执行,模拟componentDidMount。
- 当useEffect没有第二个参数时,组件的初始化和更新都会执行,模拟componentDidMount 和 componentWillUnmount。
- useEffect返回一个函数,这个函数会在组件卸载时执行。模拟componentDidMount componentWillUnmount
总之,传入依赖项,只要依赖项改变,就再次执行useEffect;不传入依赖项,初次渲染和以后的每次渲染都会调用
import React, { useState, useEffect } from "react";
let timer = null;
function App() {
const [count, setCount] = useState(0);
// 这就相当于更新--componentDidUpdate 只要依赖项改变就执行useEffect
useEffect(() => {
document.title = "componentDidMount" + count;
},[count]);
useEffect(() => {
timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// return 一个箭头函数进行清除定时器
return () => {
document.title = "componentWillUnmount";
clearInterval(timer);
};
}, []);
return (
<div>
Count: {count}
<button onClick={() => clearInterval(timer)}>clear</button>
</div>
);
}
四、 总结:useEffect 的注意点
- useEffect执行的时间点:React初次渲染和之后的每次更新渲染都会调用一遍useEffect函数,可以简单理解成class组件生命周期函数中的componentDidMount和componentDidUpdate的一个简单的合并;但是需要注意useEffect是在真实DOM完成渲染之后进行执行的;但是DidMount和DidUpdate是在真实DOM完成之前实现的;之前在类组件中,我们要用两个生命周期函数分别表示首次渲染(componentDidMonut)和更新导致的重新渲染(componentDidUpdate)。
- useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonut和componentDidUpdate中的代码都是同步执行的。
- useEffect里清理函数的执行时机是在每一次运行副作用函数之前运行即组件销毁或者调用函数前调用
useEffect使用时依赖项的选择很重要,如果有些依赖项没加上,强行用 eslint-disable-line 跳过规则校验就可能会出现capture value的问题,我最近找下之前项目中遇到的capture value 坑,给诸位看官添个乐🤣🤣🤣