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 的清除函数在每次重新渲染时都会执行,而不是只在组件卸载时执行。清除函数是在新的渲染之后执行的。

相关推荐
汪子熙18 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ26 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.4 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端
小御姐@stella6 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing6 小时前
【React】增量传输与渲染
前端·javascript·面试