【React中的闭包陷阱】

闭包陷阱

前言

以下分析按照react的函数组件写法进行

什么是闭包

函数能够访问并记住其定义时所在作用域(即使该函数在定义的作用域外执行),这个函数 + 词法环境的整体就是闭包

typescript 复制代码
// 节流实现 - 闭包
function throttle(fn, dealy) {
	let timerId; // 定时任务id
	let targetArgs; // 存储尾参数
	return function (...args) {
		targetArgs = args;
		if (!timerId) {
			timerId = setTimeout(() => {
				fn.apply(this, targetArgs)
				timerId = null
			}, dealy)
		}
	}
}
function throttleTarget(a, b) {
	return a + b;
}
const throttleEffect = throttle(throttleTarget, 500) // throttleEffect从throttle中返回,持有词法作用域变量timerId和targetArgs的引用,进而形成闭包

为什么react会存在闭包陷阱

首先react在update的时候会重新执行Component(组件函数),进而生成新的词法作用域的变量。假如你存在会锁住某一过程中词法作用域的逻辑,就会产生闭包陷阱的问题。

typescript 复制代码
// 子组件
import React, { useState, forwardRef, Ref, useImperativeHandle, useEffect, useCallback } from "react";

export interface ItemRef {
	queryData: () => void
}

const Item = forwardRef((props: { value: number }, ref: Ref<ItemRef>) => {
	const [ value, setValue ] = useState(0)
	const queryData = () => {
		console.log("触发了防抖的函数", props, isEffect && "useEffect进入");
		setValue(props.value * 2);
	}
	const queryDataCallback = useCallback(queryData, []) // 因为只会在mountd的时候执行一次,所以queryDataCallback所持有的词法作用域永远是第一次的,所以调用queryDataCallback所触发的queryData所使用的词法作用域也理所应当是第一次的,所以props.value永远是0
	useEffect(() => {
		setInterval(() => {
			queryDataCallback(true);
		}, 1000)
	}, [])
	useImperativeHandle(ref, () => {
		return queryData // 同理,因为只会初始化的时候执行一次,所以保存的queryData的指针永远是第一次执行时生成的,因此保存的词法作用域也是第一次,因此执行的时候props.value也是0
	}, [])
})

// 父组件
import React, { useState, useRef, useEffect } from "react";
const Closure = () => {
    const [ count, setCount ] = useState(0);
    const itemRef = useRef<ItemRef>(null);
    const triggerControll = () => {
        setCount(count + 1);
    };
    useEffect(() => {
        itemRef.current?.queryData();
    }, [count]);
    return (
        <div>
            <button onClick={ triggerControll }>点击次数:{count}</button>
            <Item ref={ itemRef } value={count}></Item>
        </div>
    );
};

props在更新的时候也会生成一个新的指针指向新的对象,而非在原有对象上进行的修改,所以如果这里按照指针不变来获取最新的值的话,就会遇到隐藏的闭包陷阱的问题。

如何解决闭包陷阱

要避免闭包陷阱的产生,需要时刻注意所使用的词法作用域是不是旧的。上述两种情况处理的方式尽均可以修改成监听props.value的变化来生成信息的值

typescript 复制代码
// ... 此处省略Item的其它逻辑
const queryDataCallback = useCallback(queryData, [props.value]);
useEffect(() => {
    setInterval(() => {
        queryDataCallback(true);
    }, 1000);
}, [props.value]);
useImperativeHandle(ref, () => {
    return {
        queryData
    };
}, [props.value]);
// 此处省略Item的其它逻辑 

这样就不会出现闭包陷阱的情况了

相关推荐
用户新2 小时前
V8引擎 精品漫游指南--Ignition篇(下 一) 动态执行前的事情
前端·javascript
神探小白牙7 小时前
eCharts 多系列柱状图增加背景图
javascript·ecmascript·echarts
Wect9 小时前
React 性能优化精讲
前端·react.js·性能优化
薛定猫AI14 小时前
【深度解析】Gemma Chat 本地 AI 编程 Agent:Electron + MLX + 开源模型的离线 Vibe Coding 实战
javascript·人工智能·electron
全栈前端老曹14 小时前
【前端地图】多地图平台适配方案——高德、百度、腾讯、Google Maps SDK 差异对比、封装统一地图接口
前端·javascript·百度·dubbo·wgs84·gcj-02·bd09
笑虾14 小时前
Win10 修改注册表 让鼠标悬停PNG上时 tip 始终显示分辨率
开发语言·javascript·ecmascript
雾岛听风69115 小时前
JavaScript基础语法速查手册
开发语言·前端·javascript
用户23678298016815 小时前
从零实现 GIF 制作工具:LZW 压缩与 Median Cut 色彩量化
前端·javascript
棉猴15 小时前
Python海龟绘图之绘制文本
javascript·python·html·write·turtle·海龟绘图·输出文本
Highcharts.js16 小时前
线形比赛积分增长或竞赛图|Highcharts企业图表代码示列
开发语言·前端·javascript·折线图·highcharts·竞赛图