ReactHooks:useEffect使用指南

useEffect 基本使用

useEffect 根据传参个数和传参格式,它的执行次数和执行结果是不同的。

useEffect(setup, dependencies?)

  • 在没有依赖项数组时,每次渲染之后都会执行 Effect
  • 依赖项数组可以设置多个依赖项,其中任意一项发生变化,Effect 都会执行

需要注意的是:

当依赖项是引用类型时,React 会比较依赖项的内存地址是否一样,如果一致,Effect 不会执行。示例如下:

js 复制代码
import React, { useState, useEffect } from "react";

const Child = ({ data }) => {
  useEffect(() => {
    console.log("useEffect");
  }, [data]);

  return <div>{data.x}</div>;
};

let data = { x: 0 };
const App = () => {
  const [count, setCount] = useState(0);
  console.log("render");
  return (
    <div>
      <button
        onClick={() => {
          data.x = data.x + 1;
          setCount(count + 1);
        }}
      >
        click
      </button>
      <Child data={data} />
    </div>
  );
};

export default App;

点击 click 之后,对象 data 中的属性值会发生变化,但是传入组件 <Child /> 组件的内存地址没有变化,所以 console.log("useEffect") 不会执行。为解决这个问题,应该使用对象中的属性作为依赖,而不是整个对象。应该将上面示例中组件 <Child /> 修改如下:

js 复制代码
const Child = ({ data }) => {
  useEffect(() => {
    console.log("useEffect");
  }, [data.x]);

  return <div>{data.x}</div>;
};

useEffect 的执行时机和 useLayoutEffect

useEffect在渲染完成后异步执行,不会阻塞浏览器的绘制操作。

然而并非所有的操作都适合放在 useEffect 中延迟执行。例如:在浏览器下一次绘制之前需要操作 DOM 改变页面样式,或者依赖布局信息渲染组件。如果放在 useEffect 中执行,会出现闪屏问题。而 useLayoutEffect 在浏览器执行绘制之前被同步执行,使用 useLayoutEffect 可以避免这个问题。

准确绑定依赖

js 复制代码
const Example = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
	console.log("useEffect");
	const timer = setInterval(() => {
	  console.log("setInterval");
	  setCount(count + 1);
	}, 1000)
	return () => clearInterval(timer);
  }, [])

  return <p> {count} </p>;
}

在上面例子中,useEffect 用到的依赖项 count,却没有声明在依赖项中,useEffect 不会重复执行(只在组件挂载时打印了一次 useEffect),setInterval 中拿到的 count 始终为 0,它后面每一秒都会调用 setCount(0 + 1),得到的结果始终为 1。下面是两种可以正确解决依赖的方法:

1. 在依赖项数组中包含所有在 Effect 中用到的值

将 Effect 中用到的外部变量 count 添加到依赖项数组中:

js 复制代码
useEffect(() => {
  console.log("useEffect");
  const timer = setInterval(() => {
	setCount(count + 1);
  }, 1000)

  return () => {
	console.log(`return${count}`);
	clearInterval(timer);
  }
}, [count])

可以看出依赖项数组是正确的,并且解决了上面的问题。但是也可以发现,随之带来的问题是:定时器会在每一次 count 改变后销毁和重新创建,这并不是我们想要的结果。

2. 第二种方法是修改 Effect 中的代码来减少依赖项

js 复制代码
useEffect(() => {
  console.log("useEffect");
  const timer = setInterval(() => {
	setCount((count) => count + 1);
  }, 1000)

  return () => {
	console.log('return');
	clearInterval(timer);
  }
}, [])

修改 Effect 内部的代码使 useEffect 的依赖更少,这需要一些移除依赖常用的技巧。如:setCount 还有一种函数回调模式,这种模式不需要关心当前值是什么,只需要对 "旧的值" 进行修改即可。这样就不需要把 count 写进依赖项数组这种方式来告诉 React。

是否需要清除副作用

如果只是在 React 更新 DOM 之后运行一些额外的代码,如发送网络请求等,则无需清除操作。

需要清除的是那些执行之后还有后续的操作,如定时器或者页面的监听事件等。为防止内存泄漏,可以通过 useEffect 的 return 销毁通过 useEffect 注册的监听。

js 复制代码
const Example = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
	console.log("useEffect");
	return () => {
	  console.log("return");
	}
  }, [count])

  return (
	<div>
	  <p> {count} </p>
	  {console.log("dom")}
	  <button onClick={() => setCount(count + 1)}>
		click
	  </button>
    </div>
  )
} 

从 dom、return、useEffect 的打印结果可以看出,useEffect 的清除函数在每次重新渲染时都会执行,而不是只在组件卸载时执行。清除函数是在新的渲染之后执行的。

相关推荐
疯狂的沙粒2 分钟前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员18 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐20 分钟前
前端图像处理(一)
前端
程序猿阿伟27 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒29 分钟前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪38 分钟前
AJAX的基本使用
前端·javascript·ajax
力透键背40 分钟前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
weiabc1 小时前
学习electron
javascript·学习·electron
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js