useEffect
官方定义
useEffect is a React Hook that lets you synchronize a component with an external system.
useEffect 是一个 React Hook(钩子函数),用于将组件与外部系统同步。
使用场景
- 1,根据 React 状态控制非 React 组件
- 2,设置server连接
- 3,组件出现在屏幕上时发送日志分析等
useEffect
允许你在渲染后运行一些代码,以便你可以将组件与 React 之外的某些系统同步。
用法
API: useEffect(setup, dependencies?)
来看官网的一个例子:
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
//setup
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
//cleanup function: optional
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
参数解析:
setup
: 参数为处理Effect逻辑的函数。当组件挂载到DOM上时,react会自动执行1次这个function,它还可以返回一个可选的cleanup function,在组件destroy的时候才会被调用。之后每次dependencies发生改变组件重新渲染的时候,react会用旧值先执行一次cleanup function(如果你提供了cleanup function),接着用新值再执行一次setup function。当组件从DOM上移除时,react会执行cleanup functiondependencies
: 参数为数组,可选。- 如果不传入,则只要组件一render,则Effect就会自动执行;
- 如果传入的是空数组,只会在首次挂载的时候执行,相当于
componentDidMount
- 数组不为空:state或者props参数里的值发生改变的时候才会执行
useEffect(() => {
// This runs after every render
});
useEffect(() => {
// This runs only on mount (when the component appears)
}, []);
useEffect(() => {
// This runs on mount *and also* if either a or b have changed since the last render
}, [a, b]);
useEffect
和useState
一样需要定义在组件函数体的最外层
useEffect概念解释
你可以理解为它能“勾住”函数组件中某些生命周期函数
都能勾住哪些生命周期函数?
componentDidMount
(组件被挂载完成后)、componentDidUpdate
(组件重新渲染完成后)、componentWillUnmount
(组件即将被卸载前)
为什么是这3个生命周期函数?
因为修改数据我们可以使用前面学到的useState
,数据变更会触发组件重新渲染,上面3个就是和组件渲染关联最紧密的生命周期函数。
useEffect是来解决类组件什么问题的?
useEffect是来解决类组件 某些执行代码被分散在不同的生命周期函数中 的问题。
举例1:若某类组件中有变量a,默认值为0,当组件第一次被挂载后或组件重新渲染后,将网页标题显示为a的值。 那么在类组件里,我们需要写的代码是:
//为了更加清楚看到每次渲染,我们在网页标题中 a 的后面再增加一个随机数字
componentDidMount(){
document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
}
componentDidUpdate(){
document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
}
从上面这种代码里你会看到,为了保证第一次被挂载、组件重新渲染后都执行修改网页标题的行为,相同的代码我们需要分别在componentDidMount、componentDidUpdate中写2次。
举例2:假设需要给上面那个组件新增一个功能,当组件第一次被挂载后执行一个自动累加器 setInterval,每1秒 a 的值+1。为了防止内存泄露,我们在该组件即将被卸载前清除掉该累加器。 那么在类组件里,我们需要写的代码是:
timer = null;//新增一个可内部访问的累加器变量(注:类组件定义属性时前面无法使用 var/let/const)
componentDidMount(){
document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
this.timer = setInterval(() => {this.setState({a:this.state.a+1})}, 1000);//添加累加器
}
componentDidUpdate(){
document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
}
componentWillUnmount(){
clearInterval(this.timer);//清除累加器
}
从上面代码可以看到,增加累加器和清除累加器这2个相关的执行代码被分别定义在componentDidMount、componentWillUnmount这两个生命周期函数中。
举例3:假设给上面的组件再新增一个变量 b,当 b 的值发生变化后也会引发组件重新渲染,然后呢?有什么隐患吗?
答:b 的值改变引发组件重新渲染,然后肯定是会触发componentDidUpdate函数,这时会让修改网页标题的代码再次执行一次,尽管此时a的值并没有发生任何变化。
再来回顾一下上面的3个例子:
- 1、举例1中,相同的代码可能需要在不同生命周期函数中写2次;
- 2、举例2中,相关的代码可能需要在不同生命周期函数中定义;
- 3、举例3中,无论是哪个原因引发的组件重新渲染,都会触发生命周期函数的执行,造成一些不必要的代码执行;
以上就是 类组件“某些执行代码被分散在不同的生命周期函数中”引发的问题具体表现,而useEffect就是来解决这些问题的。
useEffect函数源码:
回到useEffect的学习中,首先看一下React源码中的ReactHooks.js。
//备注:源码采用TypeScript编写,如果不懂TS代码,阅读起来稍显困难
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}