一个关于React数据不可变的无聊问题

news2024/11/27 2:32:24

对于一个React的开发者来说不知道你有没有想过为什么React追求数据不可变这个范式;

一个月前我想过一个问题如果我在使用useState这个hooks的时候传入的是一个改变后的引用类型对象会发生什么?

例如:

import {useState} from "react"

function App() {const [list,setList] = useState([0,1,2])const handleClick = ()=>{list.push(list.length)setList(list)}return (<div className="App"><button onClick={handleClick}>click me--conventionality</button>{list.map(item=><div key={item}>{item}</div>)}</div>);
}

export default App; 

然后当我们点击按钮的时候会发生什么呢?答案是从我们的视觉感官来讲什么也没有发生!列表数据一直是012; 关于这个结果我相信百分之99的react开发者都是可以预料的!也肯定有百分之80以上的人会说因为你的新数据和老数据是同一个(newState===oldState)===true在这个问题上答案也确实是这个一个。那么newState与oldState是在哪里做的比较,又是在哪里做的拦截呢?我之前想的是会在render阶段update时的reconcileChildFibers中打上effectTag标记判断前做的判断,然而当我今天在给beginWork后我发现以上这个代码压根走不到beginWork (mount阶段),带着好奇我决定从源码出发去探索一下(答案可能会有点无聊);

我们知道useState这个hooks生成

const [list,setList] = useState([0,1,2]) 

dispatchAction这个方法

mountState阶段

而useState分为两种mountStateupdateState,因为setList是在mount时被创建的所以我们先去查看他是如何被创建的

function mountState(initialState) {var hook = mountWorkInProgressHook();if (typeof initialState === 'function') {// $FlowFixMe: Flow doesn't like mixed typesinitialState = initialState();}hook.memoizedState = hook.baseState = initialState;var queue = {pending: null,interleaved: null,lanes: NoLanes,dispatch: null,lastRenderedReducer: basicStateReducer,lastRenderedState: initialState};hook.queue = queue;//创建dispatch方法并保存到链式当中//dispatch是通过dispatchSetState这个方法创建的var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);//这一步return出链式当中的list与setListreturn [hook.memoizedState, dispatch];
} 

dispatch是通过dispatchSetState这个方法创建的,然后我们去dispatchSetState中去查看

function dispatchSetState(fiber, queue, action) {//此处打上console,可以正常输出,程序可以进行到此步console.log('dispatchSetState',fiber,queue,action){if (typeof arguments[3] === 'function') {error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().');}}var lane = requestUpdateLane(fiber);var update = {lane: lane,action: action,hasEagerState: false,eagerState: null,next: null};//首屏更新走这里console.log(currentlyRenderingFiber$1===null)console.log(fiber.alternate===null)//trueif (isRenderPhaseUpdate(fiber)) {enqueueRenderPhaseUpdate(queue, update);} else {enqueueUpdate$1(fiber, queue, update);var alternate = fiber.alternate;//是否是首次更新判断(mount之后还未进入update)if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {// The queue is currently empty, which means we can eagerly compute the// next state before entering the render phase. If the new state is the// same as the current state, we may be able to bail out entirely.var lastRenderedReducer = queue.lastRenderedReducer;if (lastRenderedReducer !== null) {var prevDispatcher;{prevDispatcher = ReactCurrentDispatcher$1.current;ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;}try {//在这一步我们可以看到传入的值是已经改变的的//当前传入state(保存在链中)var currentState = queue.lastRenderedState;//第一次 [0,1,2,3]//state计算数据var eagerState = lastRenderedReducer(currentState, action); //第一次 [0,1,2,3]// Stash the eagerly computed state, and the reducer used to compute// it, on the update object. If the reducer hasn't changed by the// time we enter the render phase, then the eager state can be used// without calling the reducer again.update.hasEagerState = true;update.eagerState = eagerState;//判断newState与oldState做比较,第一次点击在这里终止if (objectIs(eagerState, currentState)) {// Fast path. We can bail out without scheduling React to re-render.// It's still possible that we'll need to rebase this update later,// if the component re-renders for a different reason and by that// time the reducer has changed.// console.log(222222,queue)return;}} catch (error) {// Suppress the error. It will throw again in the render phase.} finally {{ReactCurrentDispatcher$1.current = prevDispatcher;}}}}var eventTime = requestEventTime();var root = scheduleUpdateOnFiber(fiber, lane, eventTime);console.log('root',root)if (root !== null) {entangleTransitionUpdate(root, queue, lane);}}markUpdateInDevTools(fiber, lane);
} 

我们通过调试可以看到因为已经经过首屏更新所以走的是else内的部分,最终在else内进行当前值与计算值比较因为是同一个引用类型对象所以返回的是true

//判断newState与oldState做比较,第一次点击在这里终止
if (objectIs(eagerState, currentState)) {// Fast path. We can bail out without scheduling React to re-render.// It's still possible that we'll need to rebase this update later,// if the component re-renders for a different reason and by that// time the reducer has changed.// console.log(222222,queue)return;
} 

数据比较

function is(x, y) {return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare;
}

var objectIs = typeof Object.is === 'function' ? Object.is : is; 

最终mount阶段在dispatchSetState方法中就被拦截了,那么在update阶段又会怎么样呢?带着好奇我改写了一下demo

updateState

function App() {const [list,setList] = useState([0,1,2])//const handleClick = ()=>{list.push(3)setList(list)}const handleClick2 = ()=>{setList([...list,list.length])}return (<div className="App"><button onClick={handleClick}>click 1</button><button onClick={handleClick2}>click 2</button>{list.map(item=><div key={item}>{item}</div>)}</div>);
} 

我们先点击click2使其进入update状态,然后再点击click1,你会发现它进入了beginWork方法因为是Function组件,所以会在updateFunctionComponent 中执行,但是这这一步它停止了;原因是它在这里判断进入了bailoutOnAlreadyFinishedWork

//在这里进入bailoutOnAlreadyFinishedWork
//bailoutOnAlreadyFinishedWork 判断节点是否可复用
//当前为update阶段所以current不可能为空
//!didReceiveUpdate代表为update阶段
if (current !== null && !didReceiveUpdate) {bailoutHooks(current, workInProgress, renderLanes);console.log('bailoutOnAlreadyFinishedWork')return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} 

然后再让我们看看bailoutOnAlreadyFinishedWork 方法

function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {if (current !== null) {// Reuse previous dependenciesworkInProgress.dependencies = current.dependencies;}{// Don't update "base" render times for bailouts.stopProfilerTimerIfRunning();}markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work.console.log(renderLanes, workInProgress.childLanes)if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {console.log("stop")// The children don't have any work either. We can skip them.// TODO: Once we add back resuming, we should check if the children are// a work-in-progress set. If so, we need to transfer their effects.{return null;}} // This fiber doesn't have work, but its subtree does. Clone the child// fibers and continue. 

最终本次render阶段会在这里被强制中断

//判断子节点有无需要进行的任务操作
//在这里停止原因是workInProgress.childLanes为0导致等式成立
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {console.log("stop")// The children don't have any work either. We can skip them.// TODO: Once we add back resuming, we should check if the children are// a work-in-progress set. If so, we need to transfer their effects.{return null;}
} // This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue. 

总结

不管是在mountState阶段可变数据会在dispatchSetState时就会因为数据比对而中断,因此进入不到beginWork,在updateState阶段,可变数据会进入beginWork并根据Fibertag类型判断进入的是updateFunctionComponent还是updateClassComponent但是最终都会在bailoutOnAlreadyFinishedWork函数中因为childLanes为0的缘故终止执行;也就是说在mountState阶段不会进入render阶段,但是在updateState阶段会进入render阶段并创建fiber,但是会被中断执行

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/86002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

css之@media网页适配

原因&#xff1a; 原本是想用 iview Grid 栅格,但以下响应并不符合我的需求 【我需要的分辨率是1920和1536】&#xff0c;所以需要手动修改 解决方案&#xff1a; html <Row> <i-col :xs"6" :sm"6" :md"6" :lg"8"><d…

IB经济试卷实用指南分享

明明每个经济理论都知道&#xff0c;然而在实际答题过程中就是用不出来&#xff1f; 看到答案后却是恍然大悟&#xff0c;就像对着数学答案&#xff0c;大体还能想明白“原来是这样的”&#xff0c;但考试的时候就是想不到用这个理论、这个模型&#xff1f; 出现这种现象&#…

左值和右值

左值和右值 按字面意思&#xff0c;通俗地说。以赋值符号 为界&#xff0c; 左边的就是左值&#xff0c; 右边就是右值。 更深一层&#xff0c;可以将 L-value 的 L, 理解成 Location&#xff0c;表示定位&#xff0c;地址。将 R-value 的 R 理解成 Read&#xff0c;表示读取…

[附源码]Python计算机毕业设计电影售票管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

什么是位移电流?位移电流密度计算公式详解

位移电流变化着的电场可以存在于真空、导体、电介质中&#xff0c;本质是变化着的电场&#xff0c;不会产生化学效应&#xff0c;也不会产生焦耳热。下面一起随着小编一起了解一下什么是位移电流以及位移电流密度计算公式。 什么是位移电流&#xff1f; 1861年&#xff0c;詹姆…

[附源码]Python计算机毕业设计SSM基于web的医院门诊管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于java+springboot+mybatis+vue+mysql的一起来约苗系统

项目介绍 在系统流程分析当中调查分析它是比较重要的环节&#xff0c;因为在这个系统当中它都涉及到每个环节的业务流程&#xff0c;所以从JavaSprignBootVueMYSQL一起来约苗系统的设计的整体设计上要保证各个信息的正确输入和输出以及对数据储存的完整&#xff0c;并结合实际…

ASEMI肖特基二极管MBR20100FCT图片,MBR20100FCT大小

编辑-Z ASEMI肖特基二极管MBR20100FCT参数&#xff1a; 型号&#xff1a;MBR20100FCT 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;100V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;70V 最大直流阻断电压&#xff08;VDC&#xff09;…

python+django车辆充电桩管理系统

启动一个新项目 执行下面的命令来创建一个新的 Django 项目&#xff1a; django-admin startproject myproject 命令行工具django-admin会在安装Django的时候一起自动安装好。 执行了上面的命令以后&#xff0c;系统会为Django项目生成基础文件夹结构。 现在&#xff0c;我…

【软件测试】测试人我懵了,产品验收时还有一堆bug?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 为什么明明写了 100…

屏幕录制怎么加上水印?这个方法,真的太实用啦

随着网络技术的飞速发展&#xff0c;短视频已经成为传播信息最快的方式之一。越来越多的人将自己喜欢的游戏直播视频和网络直播课程知识制作成短视频&#xff0c;并在各大媒体平台进行分享。然后&#xff0c;录制的视频可能会被其他人带到其他平台。屏幕录制怎么加上水印&#…

FLStudio21水果软件中文版本有哪些新增功能?

FL Studio 21即将推出 – 新功能和改进。如果您从事音乐制作&#xff0c;那么您不可能没有听说过 FL Studio&#xff0c;或者很可能已经使用过这个音乐程序。好了&#xff0c;新版本的 FL Studio 21 DAW 已经准备好向公众发布了。Image-line 正在为 2022 年的大型揭幕准备最终细…

iOS原生项目拥有Flutter热重载极速调试

1. Mac的App Store上下载安装InjectionIII. 2. 打开InjectionIII, Open Project, 选择你的项目目录. 3. 选择的项目会在Open Recent中出现, 保持File Watcher的选项勾选. 4. 在AppDelegate的DidFinishLaunchingWithOptions配置InjectionIII的路径 - (BOOL)application:(UIAp…

python+django勤工助学管理系统vue

目 录 第1章 绪论 1 1.1课题背景 1 1.2 背景意义 1 1.3 研究的内容 2 第2章 相关技术 3 第3章 系统分析 5 3.1可行性分析 5 3.2系统性能分析 6 3.3系统流程分析 6 3.3.1操作流程 6 3.3.2信息添加流程 7 3.3.3信息删除流程 8 第4章 系统设…

周志华 《机器学习初步》 绪论

周志华 《机器学习初步》 绪论 Datawhale2022年12月组队学习 ✌ 文章目录周志华 《机器学习初步》 绪论一.机器学习二.典型的机器学习过程三.计算学习理论PAC模型思考两个问题问题性质角度计算要求的角度四.基本术语五.归纳偏好六.NFL定理哪个算法更好&#xff1f;NFL定理的寓意…

Codeforces Round #837 (Div. 2) A-C

A. Hossam and Combinatorics A题意&#xff1a; 给你n个数&#xff0c;让你求有多少个数对 并且 满足等于数组中最大的绝对值之差。 思路&#xff1a;直接找最大值和最小值&#xff0c;如果最大值和最小值不是一个&#xff0c;那就是最大数的数量乘最小值的数量乘2&#x…

前端基础(一)_前端页面构成

一、前端页面构成 1.HTML(Hypertext Markup Language) Html–超文本标记语言&#xff0c; 结构层由HTML标记语言创建的&#xff0c;负责页面的语义。&#xff08;它包括一系列标签&#xff0c;主要分为块标签和行标签、行内块标签三类&#xff09; 2.CSS(Cascading style sh…

绿色中国馆设计

目 录 1工程概况 1 1.1建筑设计部分 1 1.1.1设计依据 1 1.1.2设计内容、建筑面积、标高 1 1.1.3建筑空间构成 1 1.1.4采光和通风 1 1.1.5防火及安全 2 1.1.6各部分工程构造 2 1.2 结构设计部分 3 1.2.1基本资料 3 1.2.2结构形式和基础形式 3 1.2.3结构尺寸及采用的材料 4 1.2.4…

[oeasy]python0029_放入系统路径_PATH_chmod_程序路径_执行原理

放入路径 回忆上次内容 上次总算可以把 sleep.py 直接执行了 sleep.py文件头部要声明好打开方式 #!/usr/bin/python3用的是 python3 解释 sleep.py 修改 sleep.py 文件 的执行权限 给当前用户增加 执行execute 权限 chmod ux sleep.py 运行./sleep.py成功 但我不想总带着当前路…

[附源码]Python计算机毕业设计SSM基于web的网上订餐系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…