当前位置: 首页 > news >正文

【每日一面】React Hooks闭包陷阱

基础问答

问题:谈一谈你对 React Hook的闭包陷阱的理解。

产生问题的原因:JavaScript 闭包特性 + Hooks 渲染机制

闭包的本质:函数能够访问其定义时所在的词法作用域,即使函数在作用域外执行,也可以记住定义时的词法作用域的内容,后续执行时,使用这些信息。

function callback(index) {let idx = index;let op;return (type) => {op = type;console.log(op);switch (type) {case 'add':idx++;break;case 'sub':idx--;break;}return idx;}
}const fn = callback(8);
console.log(fn('add')); // 9
console.log(fn('sub')); // 8

这里的 idx 正常会在 callback 函数执行结束后释放,但是由于我们返回的是一个函数,函数中依赖这个 idx 变量,所以未能释放,此时这个变量被这个匿名函数持有,而在 fn 变量存续期间,idx 和 op 都是不会释放的,这也就形成了一个闭包。

不过经典闭包还是 for 循环

Hooks 渲染逻辑:React 组件每次渲染都是独立的快照,可以理解为,每次重新执行相关钩子的时候,组件都会重新生成一个新的作用域。

闭包陷阱:根据上面两点,React Hooks 的闭包陷阱产生过程应当是这样的,React 在渲染开始前创建了新的状态包(作用域),而我们写代码的时候无意中创建了一个闭包,持有了 React 的当前状态,再下次渲染开始时,React 重新创建了状态包,但是我们在一开始创建的闭包持有的依旧是前一次 React 创建的状态,是旧的,这就是产生闭包陷阱的根源。这里我们以一个具体例子来看:

import { useEffect, useState } from "react"const App = () => {const [count, setCount] = useState(1);useEffect(()=> {const timer = setInterval(() => console.log(count), 1000);return () => clearInterval(timer)}, []);const addOne = () => {setCount(pre => pre+1);}return (<div className="main" > <p>Hello: {count}</p><button onClick={addOne}>+1</button></div>)
}export default App

这里在组件首次渲染的时候,useEffect 帮我们设置了一个定时器,定时器执行的函数持有了外部作用域的 count 变量,产生了一个闭包。

再之后,我们在页面上点击按钮时,触发了 setCount(pre => pre+1) 状态更新,但是由于没有配置 useEffect 的更新依赖,所以定时器还是持有旧的状态包。此时打印的还是 1,没有更新。

闭包陷阱破解方式

  1. 使用 useRef:useRef 在初始化后,是一个形如 { current: xxx } 的不可变对象,不可变可以理解为,这个对象的地址不会发生变化,所以在浅层次的比较(===)中,更新后的前后对象是一个。所以取值的时候,总是能拿到最新的值。
  2. 添加 Hooks 依赖:在 useEffect 钩子的依赖列表中增加 count,当 count 发生变化的时候,会重新执行 useEffect ,内部的 timer 会重新生成,拿到最新的作用域的值。
  3. 修改 state 为一个对象:类似于 useRef,我们在更新 state 的时候,可以直接把内容写入该对象中,避免直接替换 state 对象。

扩展知识

React 官方要求我们不能将 hooks 用 if 条件判断包裹,其原因是 React 的 Fiber 架构中收集 Hooks 信息的时候是按顺序收集的,并以链表的形式进行存储的。如下示例:

function App() {const [count, setCount] = useState(0);const [isFirst, setIsFirst] = useState(false);useEffect(() => {console.log('hello init');}, []);useEffect(() => {console.log('count change: ', count);}, [count]);const a = 1;
}

示例中存在 4 个 hooks,所以 React 收集完成后形成的链表应当是这样的:

链表图

React 为链表节点设计了如下数据结构:

type Hook = {memoizedState: any,/** 省略这里不需要的内容 */next: Hook | null,
};

其中 next 就是链表节点用于指向下一个节点的指针,memoizedState 则是上一次更新后的相关 state。组件更新的时候,hooks 会严格按照这个顺序进行执行,按顺序拿到对应的 Hook 对象,所以如果我们用 if else 包裹了其中一个 hook,就会出现链表执行过程中,Hooks 对象取值错误的情况。

同样的,React 官方告诉我们,如果想在更新的时候拿到当前 state 的值,建议使用回调函数的写法,即:setCount(pre => pre + 1) 这种写法,这个原因,通过 Hook 的数据结构也大致可以判断,因为 memoizedState 存储了前一次更新的数据,使用回调时,这个 memoizedState 就可以作为参数提供给我们,并且保证总是正确的。

面试追问

  1. 能手写一个闭包吗?

参考前文代码。

  1. 使用 useRef 存储值,会有什么问题?

useRef 在初始化后,是形如 { current: xxx } 的对象,这个对象地址不会变化,所以我们监听 ref 是不起作用的,同时,和 useState 不同,useRef 内容的变更不会触发组件重新渲染。

  1. 请谈谈 hooks 在 React 中的更新逻辑?

React 是以链表形式来组织管理 hooks 的,在收集过程中按照顺序组装成链表,然后每次触发状态更新时,会从链表头开始依次判断执行更新。

  1. 那 hooks 中,useState 的更新是同步还是异步?

可以理解为异步的,展开来说,则是: state 更新函数(如触发 setCount)是同步触发的,React 执行更新(即 count 被更新)是异步的。这种设计主要是出于性能考虑,避免重复渲染,减少重绘重排。

  1. useEffect 依赖数组传空数组和不传依赖,二者有什么区别?

空数组:effect 仅在组件首次渲染时执行一次,后续不会再执行,相当于组件挂载阶段。

不传依赖:effect 会在组件首次渲染时、每次重新渲染后都执行。这种形式隐含存在渲染循环的风险,即 effect 中存在修改 state 的操作,那么按照不传依赖时执行的规则,就会陷入渲染 -> 更新 -> 触发重渲染 -> 更新 -> 触发重渲染……这样的循环。

http://www.dtcms.com/a/410471.html

相关文章:

  • 飞牛NAS的SSL证书过期,又开启了强制HTTPS,进不去界面修改SSL怎么办?
  • 黄骅住房和城乡建设局网站商丘seo公司找25火星
  • 泰州seo网站推广海南百度推广总代理商
  • 全国2023CSP-J普及组试题T1-T3
  • 电子基石:硬件工程师的器件手册 (十八) - 硬件开发流程:从概念到量产的管理艺术
  • 极客天成NVFile并行文件存储与星融元 CX-N 系列超低时延交换机完成兼容性互认证
  • AI 辅助日志分析与异常检测:从概念到实战
  • 模糊控制Fuzzy Control
  • 写了个AVIF格式转换工具,可以试试
  • Java开发者进阶之路
  • 自己弄网站怎么弄微信公众号开发微网站开发
  • 中山做展示型网站公司网站建设平台
  • 建网站的大公司php多语言网站开发
  • 第四部分:VTK常用类详解(第86章:Infovis模块 - 信息可视化类)
  • .NET操作Excel:高级格式设置
  • H618-源码编译与镜像文件下载
  • 40、企业智能决策引擎:BI分析平台的架构设计与业务实践
  • 软件开发公司如何利用大数据可视化设计提升决策效率
  • 北师大网页制作与网站建设wordpress seo模块
  • 外贸网站免费模板顺企网官网企业名录
  • iOS 26 崩溃日志深度指南,如何收集、符号化、定位与监控
  • C#实现CRC8、CRC16、CRC32校验算法
  • 大型语言模型(LLM)分类与特性全解析
  • iOS 26 兼容测试实战,机型兼容、SwiftUI 兼容性改动
  • qt everywhere souce code编译
  • seo网站推广是什么云服务器哪家好
  • 云手机和网盘之间的区别都有哪些
  • 企业级 K8s 运维实战:集群搭建、微服务暴露(Ingress)、监控告警(Prometheus)全流程
  • ImmuCellAI 免疫浸润分析
  • 【vLLM 学习】Neuron Int8 Quantization