解决React中的Hooks闭包陷阱(资深前端工程师的干货教程)

避免React中的Hooks闭包陷阱

在 React 中使用 Hooks 是一种强大而灵活的方式来管理组件的状态和副作用。然而,当使用 useStateuseEffect 时,我们必须小心处理闭包陷阱,以避免出现意外的行为。

什么是闭包陷阱?

闭包是指一个函数可以访问其词法作用域之外的变量。在 React 中,当在 useEffectuseState 的回调函数中引用状态或 props 时,就会创建闭包。如果不小心处理,闭包可能导致意外的行为,甚至成为 bug 的源头。

React Hooks 中的闭包陷阱主要会发生以下几种情况:
  • 在 useState 中的闭包陷阱
  • 在 useEffect 中的闭包陷阱
  • 在 useCallback 中的闭包陷阱
useState 陷阱

1. 陷阱:【异步陷阱】

export default function Hooks() {
	const [count, setCount] = useState(0);
	const add = () => {
		setCount( count + 1 );
		console.log(count, '修改前的值');
		}
		return (
			<div>
				<span>{count}</span>
				<button onClick={ add }> + </button>
			</div>
			);
		}
出现的问题:
  • 点击添加按钮,发现值更新了,打印的值却还是上次的
  • useState 修改状态 是异步执行无法获取更新后的值
解决方案:
  • 所以我们不能修改后,把值拿去其他操作 (应该拿 count+1)
  • 可以通过useRef 获取最新值

修改后代码:

export default function Hooks() {
	const [count, setCount] = useState(0);
	const countRef = useRef()
	countRef.current = count
	const add = () => {
		setCount( count => count + 1 );
		setTimeout(() => { // 模拟异步请求
			console.log(count, '修改前的值');
			console.log(countRef.current, '修改后的值');
				}, 0)
			}
		return (
			<div>
				<span>{count}</span>
				<button onClick={ add }> + </button>
			</div>
				);
		}

2. 陷阱:【只更新最后1个】
传入一个基于状态的值

import React, { useState } from 'react'
export default function Hooks() {
	const [count, setCount] = useState(0);
	const add = () => {	
		console.log("value1: ", count)
		setCount( count + 1 );
		console.log("value2: ", count)
		setCount( count + 2 );
		console.log("value3: ", count)
		setCount( count + 3 );
		console.log("value4: ", count)
	}
	return (
		<div>
			<span>{count}</span>
			<button onClick={ add }> + </button>
		</div>
		);
	}
  • 打印可以看出如果我们传入的是一个普通值,他只会进行最后一次更新

传入一个函数

const add = () => {
	console.log("value1: ", count)
	setCount( count => count + 1 );
	console.log("value2: ", count)
	setCount( count => count + 2 );
	console.log("value3: ", count)
	setCount( count => count + 3 );
	console.log("value4: ", count)
}
  • 可以看出,传入一个函数的话,它会进行两次赋值,因为它更新的值是基于之前的值来执行,所以在开 发中推荐使用函数传入的形式进行修改
useEffect 陷阱

1. 陷阱:【过期闭包】

import React, { useState, useEffect } from 'react'
export default function Hooks3() {
	const [count, setCount] = useState(0);
		useEffect(() => {
			setInterval(() => {
			   setCount(count + 1)
			}, 1000)
		}, [])
	useEffect(()=>{
		setInterval(() => {
			console.log(`Count: ${count}`)
		}, 1000);
	}, []);
	return (
		<div>
			<span>{count}</span>
		</div>
		);
	}
  • 页面上count一直显示1
  • useEffect的第二个参数为空数组,所以只会在组件加载后仅执行一次,我们知道组件每次render的
    时候都会生成一个新的state对象,对应一个快照,上述代码中,因为useEffect只执行了一次,所
    以定时器中的 count 一直是最初快照里的 count ,那么页面中 count 的显示肯定不会改变
  • 闭包陷阱产生的原因就是 useEffect 的函数里引用了某个 state,形成了闭包(也有叫过时的闭包)

2. 解决方法

  • useEffect第二个参数添加依赖项count ,
  • 并且每次更新,添加计时器,清除计时器

解决后代码:

import React, { useState, useEffect } from 'react'
export default function Hooks() {
	const [count, setCount] = useState(0);
	useEffect(() => {
		const timer = setInterval(() => {
			setCount(count + 1)
		}, 1000)
		return () => clearInterval(timer)
	}, [count])
	useEffect(()=>{
		const timer = setInterval(() => {
			console.log(`Count: ${count}`)
		}, 1000);
		return () => clearInterval(timer)
		}, [count]);
		return (
			<div>
				<span>{count}</span>
			</div>
		);
}

扩展知识:

  • 使用 useEffect 时,若有多个副作用,则应该调用多个 useEffect ,而不是写在一个里面;
  • useEffect 第一个参数可以返回一个函数,这个函数会在组件卸载时(也就是render了,生成新的快照时)执行,可以用来清除副作用里的操作;
useCallback 陷阱
  • useCallback 本来拿来优化性能,当依赖变化不用重新注册该函数
  • 使用不当也会,出现一定的问题

1. 陷阱:【获取父组件的值,不是最新】

import React, { useCallback, useState } from 'react'
function Child(props) {
	let log = useCallback(() => {
		console.log(props.count, '子组件打印的props')
	}, [])
	return (
		<>
			count: {props.count}
			<button onClick={() => log()}> 打印 </button>
		</>
	  );
	}
	
	export default function Parent() {
		const [count, setCount] = useState(0);
		return (
			<>
				<button onClick={() => setCount(count+1)}> +1 </button>
				<Child count={count} />
			</>
		);
}
  • 此时我们在 父组件点击 增加按钮
  • 子组件的 count 发生改变 ,我们在点击打印按钮,发现count 一直是0
  • 说明useCallback 依赖为[]数组,取到count 已经过期了

2. 解决方法

  • 给useCallback第二个参数添加依赖的数据,依赖变化了函数会重新生成
let log = useCallback(() => {
console.log(props.count, '子组件打印的props')
}, [props.count])
总结

在 React 中,使用 Hooks 是一种高效和灵活的方式来管理状态和副作用。但是,在使用 useState 和 useEffect 时,务必小心处理闭包陷阱。通过使用回调函数更新状态,以及在 useEffect 中正确地处理依赖项,可以有效地避免闭包陷阱,确保组件行为的可靠性和一致性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值