【React】常见疑问的整理(学习笔记)

这篇具有很好参考价值的文章主要介绍了【React】常见疑问的整理(学习笔记)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. React渲染一个列表,需要为每一项指定key?

        列表的key主要是为了react渲染阶段更好的协调(reconciliation),diff时更好的识别react元素,减少dom的创建和操作,提升效率。

设定key值方式:

  • 来自数据库的数据,最好是主键(Primary Key)
  • 如果数据是本地产生,可以生成唯一的ID作为key

key需满足条件:

  • key值在兄弟节点之间必须唯一
  • key值不能改变
2. 为什么一定要在函数组件(FC)顶层调用Hook函数?

        以useState举例来说,state和修改state的操作队列一起存在Fiber节点的hooks数组中,形式类似。

fiber.hooks = [
    {
        state: '', // 第0个hook的当前状态值
        queue: [], // 第0个hook待执行的操作队列
    },
    {
        state: '', // 第1个hook的当前状态值
        queue: [], // 第1个hook待执行的操作队列
    },
    {
        memorizedState: [...,...]
    }
    ...
];

渲染时,函数组件作为函数执行时,函数内每执行一个hook函数,就按顺序从fiber.hooks取出hook的对应状态和操作队列。如果hook函数不是在函数组件顶层,可能会打乱获取hook对应状态和操作队列的顺序。

3.React运行过程是怎样的?
3.1 render阶段(可中断的递归

一、基本过程

  • render
  • performSyncWorkOnRoot 同步(performConcurrentWorkOnRoot 异步)
  • wookLoopSync(workLoopConcurrent)
  • performUnitOfWork:创建下一个Fiber节点,并赋值给workInProcess,将workInProcess与已创建的FIber节点连接起来,构成Fiber树
  • beginWork():根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来,当遍历到叶子节点时就会进入“归”阶段
  • completeWork():当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(fiber.sibling !== null),会进入其兄弟Fiber的‘递’阶段;如果不存在兄弟Fiber,会进入父级Fiber的‘归’阶段。
  • ‘递’和‘归’阶段交错执行直到‘归’到rootFiber,至此渲染工作结束。
  • 深度优先:child -> sibling -> return(parent),一直child到叶节点,再到sibling兄弟节点,处理完了再到return父节点

二、beginWork(current | null, workInProgress, renderLanes)

  • update:current.memorizedProps(oldProps)和workInProgress.pendingProps(newProps)
  • mount:根据Fiber.tag不同,进入不同类型的Fiber的创建逻辑

最终,都会进入reconcileChildren方法

  • update:reconcileChildren会将当前组件与该组件上次更新对应的Fiber节点比较(Diff算法),将比较的结果生成新Fiber节点(执行reconcileChildFibers
  • mount:reconcileChildren会创建新的子Fiber节点(执行moutChildFibers

不论走哪个逻辑,最终他会生成新的子Fiber节点并赋值给workInProgress.child,作为本次beginWork返回值 (opens new window),并作为下次performUnitOfWork执行时workInProgress的传参 (opens new window)。

最后会执行moutChildFibersreconcileChildFibers,workInProgress.child = moutChildFibers(...)或reconcileChildFibers(...),其中reconcileChildFibers会为生成的Fiber节点带上effectTag属性,moutChildFibers则不会。

fiber.stateNode会在completeWork中创建,mount时只有rootFiber会赋值Placement effectTag,在commit阶段直接一次插入操作。

三、completeWork(current | null, workInProgress, renderLanes)

类似beginWork,completeWork也是针对不同fiber.tag调用不同的处理逻辑

  • update:Fiber节点已存储对应DMO节点,不需要再生成DOM节点
  1. onClick、onChange等回调函数的注册
  2. 处理style prop
  3. 处理DANGEROUSLY_SET_INNER_HTML prop
  4. 处理children prop

fiber.tag为HostComponent时,最主要的逻辑是调用updateHostComponent,在updateHostComponent内部,被处理完的props会赋值给workInProgress.updateQueue,并最终会在commit阶段渲染到页面上。

  • mount
  1. 为Fiber节点生成对应DOM节点
  2. 将子孙DOM节点插入刚生成的DOM节点中
  3. 与update逻辑中的updateHostComponent类似的处理props的过程

mount时,只会在rootFiber存在Placement effectTag,那么commit阶段是如何通过一次插入DOM操作的?原因是completeWorkd中appendAllChildren方法,由completeWork属于‘归’阶段调用函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下,‘归’到rootFiber时,已经构建好了离屏DOM树。

completeWork的上层函数completeUnitOfWork中,每执行完completeWork且存在effectTag的Fiber节点会被保存在一条被称为effectList的单向链表中。

在‘归’阶段,所有有effectTag的Fiber节点都会被追加在effectList中,最终形成一条以rootFiber.firstEffect为起点的单向链表。rootFiber.firstEffect.nextEffect.nextEffect....

结尾:render全部工作完成,在performSyncWorkOnRoot函数中,fiberRootNode被传递给commitRoot方法,开启commit阶段工作流程。

3.2 commit阶段

commitRoot(root) commit阶段的起点。

一、commit阶段主要工作

  • before mutation阶段(执行DOM操作前)
  • mutation阶段(执行DOM操作)
  • layout阶段(执行DOM操作后)

before mutation阶段之前和layout阶段之后,还有一些额外工作:useEffect的触发、优先级相关的重置、ref的绑定/解绑

before mutation阶段之前:主要做一些变量赋值,状态重置的工作

layout阶段之后

  • useEffect相关的处理。
  • 性能追踪相关。
  • 在commit阶段会触发一些生命周期钩子(如 componentDidXXX)和hook(如useLayoutEffect、useEffect)

各阶段的工作

  • before mutation阶段

        整个过程就是遍历effectList,并调用commitBeforeMutationEffects函数处理,

  1. 处理DOM节点渲染/删除后的utoFocus、blur逻辑
  2.  调用getSnapshotBeforeUpdate生命
  3. 周期钩子
  4. 调度useEffect(异步执行原因主要是为了防止同步执行时阻塞浏览器渲染)

  • mutation阶段

        mutation阶段也是遍历effectList,并调用commitMutationEffects函数处理

  1. 根据ContentReset effectTag重置文字节点
  2. 更新ref
  3. 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating)

Placement effect

commitPlacement:getHostParentFiber、getHostSibling->insertOrAppendPlacementNodeIntoContainer、insertOrAppendPlacementNode

Update effect

commitWork:

commitHookEffectListUnmount遍历effectList,执行所有useLayoutEffect hook的销毁函数

commitUpdate最终会在updateDOMProperties (opens new window)中将render阶段 completeWork (opens new window)中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。

Deletion effect

commitDeletion

  • 递归调用Fiber节点及其子孙Fiber节点中fiber.tag为ClassComponent的componentWillUnmount (opens new window) 生命周期钩子,从页面移除Fiber节点对应DOM节点
  • 解绑ref
  • 调度useEffect的销毁函数

  • layout阶段

        与前两个阶段类似,layout阶段也是遍历effectList,并调用commitLayoutEffects函数处理

  1. commitLayoutEffectOnFiber(调用生命周期钩子和hook相关操作):componentDidMount、componentDidUpdate、this.setState回调函数、调度useEffect的销毁和回调函数(先调度、layout完成后再异步执行)、useLayoutEffect回调函数(mutation阶段调用useLayoutEffect的销毁函数、layout阶段同步执行回调)
  2. commitAttachRef(赋值 ref)

结束:root.current = finishedWork;

3.3 Diff算法

一、在React中,与DOM节点相关的4个节点

  1. current Fiber
  2. workInProcess Fiber
  3. DOM节点本身
  4. JSX对象

Diff算法就是根据1和4,生成2。

二、三个限制规则

1. 仅同级元素进行Diff,跨级则不复用

2. 不同类型元素产出不同的树,如果元素div变为p,则销毁div及其子孙节点,新建p及其子孙节点

3. 通过key prop来暗示哪些子元素不在同的渲染下保持稳定(复用DOM节点)

三、如何实现

        从Diff的入口函数reconcileChildFibers出发,根据newChild(即JSX对象)类型调用不同的处理函数

  1. 当newChild类型为object、number、string,代表同级只有一个节点
  2. 当newChild类型为Array,同级有多个节点

针对1同级只有一个节点的Diff,以类型Object为例,会进入reconcileSingleElement(对于number和string是reconcileSingleTextNode),基于过程如下。

【React】常见疑问的整理(学习笔记),前端开发,react.js,前端,javascript

判断DOM节点复用:

1. 判断上一次更新时的Fiber节点是否存在对应DOM节点,不存在跳转到第8步

2. 上一次更新存在对应DOM节点,判断是否复用

3. 首先比较key是否相同 child.key === element.key

4. key相同,接下来比较type是否相同

5. type相同,表示可以复用,child.elementType === element.type,将上次更新的Fiber的副本作为本次新生成的Fiber节点并返回

6. key相同但type不同,将该fiber节点及其兄弟fiber节点标记删除

(注:连接key相同的节点type都不一样,则不再遍历兄弟Fiber节点,全部标记删除)

7. key不同,将该fiber节点标记删除

(注:key不同时,只表示当前Fiber不能复用,还有其兄弟节点还没遍历,只当前fiber标记删除)

8. 创建新fiber并返回

针对2同级具有多个节点的Diff,会进入reconcileChildrenArray

  • 节点更新:节点属性变化、节点类型更新(优先级高)
  • 节点新增或减少:添加或减少节点
  • 节点位置变化:调整节点顺序

第一轮遍历:处理更新的节点

  1. let i = 0,遍历newChildren,将newChildren[i]和oldFiber比较,判断是否可复用
  2. 如果可复用,i++,继续比较newChildren[i]和oldFiber.sibling,可以复用则继续遍历
  3. 不可复用,分两种情况:① key不同导致的不可复用,跳出遍历,第一轮遍历结束;② key相同type不同导致的不可复用,将oldFiber标记删除,并继续遍历
  4. 如果newChildren遍历完了或者oldFiber遍历完了,跳出遍历,第一轮遍历结束

步骤3跳出的遍历,newChildren和oldFiber没有遍历完,等待第二轮遍历

步骤4跳出的遍历,可能newChildren或oldFiber没有遍历完,也可能都遍历完了,等待第二轮遍历

第二轮遍历:处理剩下的不属于更新的节点

  1. newChildren与oldFiber同时遍历完:只需在第一轮遍历进行组件更新,Diff结束
  2. newChildren没遍历完,oldFiber遍历完:说明已有DOM都复用了,本次更新有新节点插入,只需遍历剩下的newChildren生成对应workInProgress Fiber节点,标记Placement
  3. newChildren遍历完,oldFiber没遍历完:说明本次更新比之前节点少了,遍历剩下的oldFiber,标记Deletion
  4. newChildren与oldFiber都没有遍历完:说明有节点在这次更新中改变了位置

处理移动节点:

为了快速找到key对应的oldFiber,我们将所有未处理的oldFiber存入以key为Key,oldFiber为value的Map中(existingChildren)。接下来遍历剩余的newChildren,通过newChildren[i].key就能在existingChildren中找到key相同的oldFiber,通过比较可复用节点的index,对oldFiber进行向右移位。

3.4 状态更新

触发更新

  • ReactDOM.render
  • this.setState
  • this.forceUpdate
  • useState
  • useReducer

每次状态更新,都会创建一个保存更新状态相关内容的对象Update,render阶段的beginWork中会根据Update计算新的state

render阶段是从rootFiber开始向下遍历,那如何从触发状态更新的Fiber得到rootFiber呢?

答案是调用markUpdateLaneFromFiberToRoot方法,从触发状态更新的Fiber一直向上遍历到rootFiber,并返回rootFiber,其中遍历过程中会更新遍历到的Fiber优先级。

当前拥有的rootFiber,其对应的Fiber树中某个Fiber节点包含一个Update,接下来通知Scheduler根据更新优先级,决定以同步还是异步的方式调度本次更新(ensureRootIsScheduled)。

一、更新流程

触发状态更新 -> 创建Update对象 -> 从fiber到root(markUpdateLaneFromFiberToRoot) -> 调度更新(ensureRootIsScheduled->scheduleSyncCallback或scheduleCallback) -> render阶段(performSyncWorkOnRoot或performConcurrentWorkOnRoot)

ReactDOM.render没有优先级概念,ReactDOM.createBlocking和ReactDOM.createRoot创建的应用采用并发的方式更新状态,高优更新会中断低优更新,先完成render -> commit流程,等高优更新完成后,低优更新基于高优更新的结果重新更新

二、Update的分类

  • ReactDOM.render - HostRoot
  • this.setState - ClassComponent
  • this.forceUpdate - ClassCoomponent
  • useState - FunctionComponent
  • useReducer - FunctionComponent

HostRoot和ClassComponent共用一套Update结构,FunctionUpdate单独一套Update结构,结构不同,工作流程大体相同。

// HostRoot和ClassComponent共用一套Update结构
// 由createUpdate方法返回
const update: Update<*> = {
  eventTime, // 任务时间
  lane, // 优先级相关字段
  suspenseConfig, // suspense相关
  tag: UpdateState, // 更新的类型 UpdateState|ReplaceState|ForceState|CaptureUpdate
  payload: null, // 更新挂载的数据 ClassComponent payload为this.stateState第一个传参,HostRoot payload为ReactDOM.render的第一个传参
  callback: null, // 更新的回调函数

  next: null, // 与其他Update形成链表
}

Fiber节点上多个Update会组成链表并被包含在fiber.updateQueue中。 

// UpdateQueue由initializeUpdateQueue方法返回
const queue: UpdateQueue<State> = {
  baseState: fiber.memoizedState, // 本次更新前该Fiber节点的state,Update基于该state计算更新后的state
  firstBaseUpdate: null,
  lastBaseUpdate: null,
  /*本次更新前该Fiber节点已保存的Update。以链表形式存在,链表头为firstBaseUpdate,链表尾为lastBaseUpdate。之所以在更新产生前该Fiber节点内就存在Update,是由于某些Update优先级较低所以在上次render阶段由Update计算state时被跳过*/
  shared: {
    pending: null, // 触发更新时,产生的Update会保存在shared.pending中形成单向环状链表
  },
  effects: null, // 保存update.callback !== null的Update
};

三、更新优先级

  • 生命周期方法:同步执行
  • 受控用户输入:比如输入框内输入的文字,同步执行
  • 交互事件:比如动画,高优先级执行
  • 其他:比如数据请求,低优先级执行

每当需要调度,React会调用Scheduler提供的方法runWithPriority,该方法接收优先级常量和一个回调函数作为参数。回调函数会以优先级高低为顺序排列在一个定时器中,并在合适的时间触发。传递的回调函数一般为render阶段的入口函数。

四、ReactDOM.render

  1. ReactDOM.render
  2. 创建fiberRootNode、rootFiber、updateQueue(legacyRenderSubtreeIntoContainer)
  3. 创建Update对象(updateContainer)
  4. 从fiber到root(markUpdateLaneFromFiberToRoot)
  5. 调度更新(ensureRootIsScheduled)
  6. render阶段(performSyncWorkOnRoot或performConcurrentWorkOnRoot)
  7. commit阶段(commitRoot)

五、this.setState

this.setState -> this.updater.enqueueSetState

this.forceUpdate -> this.updater.enqueueForceUpdate

3.5 Hooks

一、Update数据结构

const update = {
  action,
  next: null
}

Hook数据结构

hook = {
  // 保存update的queue,即上文介绍的queue
  queue: {
    pending: null
  },
  // 保存hook对应的state
  memoizedState: initialState,
  // 与下一个Hook连接形成单向无环链表
  next: null
}

Hook状态和操作存放在一个单向无环链表中,每执行完一个Hook函数,workInProgressHook.next找到一下HooK的状态和操作。

参见文章:

React技术揭秘 (iamkasong.com)

Build your own React (pomb.us)

注:本篇仅为学习笔记,如有不合理之处,还请帮忙指出,大家一起交流学习~文章来源地址https://www.toymoban.com/news/detail-820824.html

到了这里,关于【React】常见疑问的整理(学习笔记)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端框架学习-React(一)

    React 应用程序是由 组件 组成的。 react 程序是用的jsx语法,使用这种语法的代码需要由babel进行解析,解析成js代码。 jsx语法: 只能返回一个根元素 所有的标签都必须闭合(自闭和或使用一对标签的方式闭合) 使用驼峰式命名法给大部分属性命名如:className 大写字母开头的

    2024年02月12日
    浏览(30)
  • react学习笔记——1. hello react

    包含的包一共有4个,分别的作用如下: babel.min.js:可以进行ES6到ES5的语法转换;可以用于import;可以用于将jsx转换为js。注意,在开发的时候,这个转换(jsx转换js)不在线上使用,因为转换需要时间,页面可能会出现白屏。 react.development.js:react的核心代码库,引入以后,

    2024年02月14日
    浏览(25)
  • React学习笔记01-React的基本认识

    React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决 定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源 了。 轻量级的视图层库!A JavaScript library for building user interfaces React不是一个完整的

    2024年02月08日
    浏览(26)
  • 【React Router】React Router学习笔记

    React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。 React Router 知道如何为我们搭建嵌套的 UI,因此我们不用手动找出需要渲染哪些 Child 组件。 获取URL参数。当渲染组件时,React Router 会自动向 Route 组件

    2024年02月08日
    浏览(26)
  • 【前端知识】React 基础巩固(十九)——组件化开发(三)

    Main.jsx TabControl/index.jsx TabControl/style.css

    2024年02月13日
    浏览(40)
  • React前端开发架构:构建现代响应式用户界面

    在当今的Web应用开发中,React已经成为最受欢迎的前端框架之一。 它的出色性能、灵活性和组件化开发模式,使得它成为构建现代响应式用户界面的理想选择。在这篇文章中,我们将探讨React前端开发架构的核心概念和最佳实践,以帮助您构建出色的Web应用。 组件化开发:构

    2024年02月12日
    浏览(43)
  • 【前端知识】React 基础巩固(十七)——组件化开发(一)

    什么是组件化开发? 分而治之的思想 将一个页面拆分成一个个小的功能块 将应用抽象成一颗组件树 React的组件相对于Vue更加的灵活和多样 按照不同的方式可以分为很多类组件 根据 组件的定义方式 ,分为: 函数组件 、 类组件 根据 组件内部是否有状态需要维护 ,分为:

    2024年02月12日
    浏览(57)
  • 前端开发框架生命周期详解:Vue、React和Angular

    作为前端开发者,掌握前端开发框架的生命周期是非常重要的。在现代Web应用开发中,Vue.js、React和Angular是三个最流行的前端开发框架。本篇博客将详细解读这三个框架的生命周期,包括每个阶段的含义、用途以及如何最大限度地利用它们。通过详细的代码示例和实用的技巧

    2024年02月13日
    浏览(41)
  • 【学习前端第七十四课】React生命周期

    我们这里所说的生命周期是基于类式组件的,其中主要分为三个阶段 挂载 更新 卸载 以上每一个阶段内都有自己的一系列在当前生命周期中可被自动执行的生命周期函数(类似于vue的钩子函数) 挂载阶段(初始化) 在挂载阶段按照执行顺序分别有以下几个生命周期函数 co

    2024年02月21日
    浏览(30)
  • 丁鹿学堂:前端学习进阶指南之react入门(react在html中使用数据绑定和修改)

    在html中使用react 今天跟大家分享react的基础语法。 我们采用最简单的方法,就是在html中引入react 因为一上来就使用脚手架的话,很多配置大家不一定清楚。 而在html中使用react的话,可以直接去学习react最基本的语法。 这是零基础学习react的最佳实践。 引入react的依赖 react也

    2024年02月14日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包