ReactHooks(2):useEffect使用指南

useEffect 基本使用

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

useEffect(setup, dependencies?)

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

需要注意的是:

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

import React, { useState, useEffect } from "react";

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

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

let data = { x: 0 };
const Demo = () => {
  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 Demo;

结果如下:
在这里插入图片描述

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

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

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

在这里插入图片描述

useEffect 的执行时机

useEffect 和 useLayoutEffect

useLayoutEffect 的使用方法和 useEffect 相同,区别是它们的执行时机。

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

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

对比 useEffect 和生命周期

不建议用生命周期的思路去思考 useEffect 的执行过程,因为它们的心智模型是不同的。

函数组件中不存在生命周期, React 会根据当前的 props 和 state 同步 DOM,每次渲染都会被固化,包括 state, props,side effects 以及写在 函数组件中的所有函数。

所以 useEffect 可以看作每一次渲染后的一个独立的函数,可以接受 props 和 state,并且接受的 props 和 state 是当前 render 的数据,是独立的。相对于生命周期 componentDidMount 中的 this.state 始终指向最新数据,useEffect 中不一定是最新的数据,更像是渲染的一部分 —— 每个 useEffect 属于一次特定的渲染

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
	setTimeout(() => {
	  console.log(`You clicked ${count} times`);
	}, 3000)
  })

  return (
    <div>
	  <p> You clicked {count} times </p>
	  <button onClick={() => setCount(count+1)}> Click </button>
	</div>
  )  
}

结果如下:
在这里插入图片描述
在 Class 组件中使用生命周期,代码示例:

componentDidUpdate(){
  setTimeout(() => {
	console.log(`You clicked ${this.state.count} times`);
  }, 3000)
}

结果如下:
在这里插入图片描述

准确绑定依赖

const Demo = () => {
  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 添加到依赖项数组中:

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

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

在这里插入图片描述

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

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

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 注册的监听。

const Demo = () => {
  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>
  )
} 

结果如下:
在这里插入图片描述

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

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值