Skip to main content

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 function

  • dependencies: 参数为数组,可选。

    • 如果不传入,则只要组件一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]);
注意

useEffectuseState一样需要定义在组件函数体的最外层

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);
}