惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
O
OpenAI News
D
Docker
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 司徒正美
C
CXSECURITY Database RSS Feed - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
K
Kaspersky official blog
Security Latest
Security Latest
T
Tailwind CSS Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Vulnerabilities – Threatpost
W
WeLiveSecurity
N
News and Events Feed by Topic
aimingoo的专栏
aimingoo的专栏
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cyber Attacks, Cyber Crime and Cyber Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
爱范儿
爱范儿
宝玉的分享
宝玉的分享
腾讯CDC
H
Heimdal Security Blog
Webroot Blog
Webroot Blog
AI
AI
WordPress大学
WordPress大学
Recorded Future
Recorded Future
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
C
Check Point Blog
TaoSecurity Blog
TaoSecurity Blog
Cisco Talos Blog
Cisco Talos Blog
The Cloudflare Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - Franky
云风的 BLOG
云风的 BLOG

博客园 - 张润昊

大模型为什么会产生幻觉?从原理到工程防御 Codex 的 Skill 到底是怎么被调用的? Superpowers:如何约束 Coding Agent 使用 Codex 的一次危险提醒 我的Nginx 的配置分析 NextJS 安全漏洞分析 AI工具学习02 - 使用 ChatGPT 进行 PRD 产品设计 NextJS 02 - 服务端渲染 NextJS01 - page router - 问题列表 Flutter02-布局 Flutter01-本地数据库 isar React 学习 03-两种 Api 的使用 React 学习 02-commit 渲染 React 学习 01-时间切片 安卓低机型卡顿分析以优化方案 命令行 查找某个结尾的文件. 总结下vim快捷键 React技术揭密学习(二) Build your own React 前端监控系统博客总结 记账项目 - webpack优化 React源码解携(二): 走一趟render流程 macos更新后, MySQL不能启动问题
React技术揭密学习(一)
张润昊 · 2022-02-02 · via 博客园 - 张润昊

学习React技术揭秘

React15架构

  • Reconciler: 协调器 - render 找出有变化的组件 - diff
  • Renderer: 渲染器 - commit 渲染有变化的组件

15 - Reconciler

  • 触发更新api
    • this.setState
    • this.forceUpdate
    • ReactDom.render
  • 更新流程:
    • 调用函数组件, 或者class组件的render, 转换jsx为虚拟dom
    • 虚拟dom与上次更新的dom对比 (diff1)
    • 通过对比找出本次更新中变化的dom (diff2)
    • 通过Renderer将渲染变化的dom

15 - Renderer

  • Reconciler通知Renderer进行更新
  • 也就是render后调用commit函数

缺点

  • mount调用mountComponent
  • update调用updateComponent
  • 两个方法都是递归更新组件
  • 递归一旦开始就无法, 中途无法中断, 超过16ms, 用户感到卡顿
  • 使用异步可中断的更新代替同步的更新
  • Reconciler和Renderer交替执行.
  • 第一个组件的Renderer工作完成后, 再交给第二个组件的Reconciler
  • 整个过程是同步的

React16

  • Scheduler: 调度任务的优先级, 高任务优先进入Reconciler -- schedule
  • Reconciler: 找出变化的组件 -- render
  • Renderer: 渲染变化的组件 -- commit

16 - Scheduler

  • 以浏览器是否有剩余时间为标准, 当浏览器有剩余时间的时候, 通知我们.
  • 类似requestIdleCallback:
    • 浏览器兼容性
    • 触发频率不稳定: 例如切换tab后, 之前的tabrequestIdleCallback处罚频率变得很低
  • React自己实现了一套scheduler
    • 浏览器空闲时间处罚回调
    • 多种调度优先级

16 - Reconciler

  • 递归调用变为可中断的循环过程
  • 每次循环判断当前是否有剩余时间
  • Reconciler和Renderer不再是交替工作
    • Scheduler将任务交给Reconciler
    • Reconciler将变化的虚拟dom打上标记 增/删/更新
    • 所有组件的都完成了Scheduler和Reconciler后, 再交给Renderer
  • Renderer根据tag同步执行dom操作
  • Scheduler和Reconciler可能被打断的原因:
    • 有其他高优先级任务需要更新
    • 当前帧没有剩余时间
    • 因为是在内存中工作, 反复被打断也不会显示在页面中

Fiber心智模型

  • 代数效应: 函数式编程的一个概念. 将副作用从函数中抽离, 使函数关注点保持纯粹
  • React中的: useState, useReducer, useRef
    • 不需要关注react中如何保存的
    • 只需要useState返回的是我们想要的state即可
  • 异步可中断更新:
    • 更新在执行过程中可能会被打断(时间分片用尽/更高任务插入)
    • 可以继续执行时恢复之前执行中间态
  • Generator:
    • 具有传染性
    • 执行的中间态上下关联
    • 只适合处理单一优先级任务的中断和执行
  • 纤程(fiber), 进程(process), 线程(thread), 协程(Coroutine).程序执行过程
  • 协程实现(Generator), 纤程实现(fiber), 代数效应在js中的实现
  • React Fiber
    • 内部实现一套状态更新机制.
    • 支持任务不同优先级, 可中断和恢复.
    • 恢复之后复用之前的中间状态.
    • 每个任务单元为ReactElement对应的Fiber节点

Fiber实现原理

  • React15使用采用递归的方式创建和更新虚拟dom, 递归的状态不能中断. 如果组件层次深的话, 递归会占用线程很多的时间.

  • React16使用Fiber架构, 将递归的无法中断的更新重构为异步可中断更新

  • 含义

    • React15, 数据保存在递归调用栈中, 称为stack reconciler, React16中的Reconciler基于fiber节点, 称为Reconciler Fiber
    • 静态数据结构: 每个Fiber, 对应一个ReactElement, 保存了该组件的类型(FunctionComponent/ClassComponent/HostComponent), 对应的DOM节点信息
    • 动态工作单元: 每个Fiber节点保存了本次更新中该组件改变的状态, 要执行的工作(被删除/被插入到页面中/被更新).
  • 作为架构:

// 指向父级Fiber节点
/**
 * return 指的是当前节点执行完`completeWork`后, 返回的下一个工作单元
 */
this.return = null;
// 指向子Fiber节点
this.child = null;
// 指向右边第一个兄弟Fiber节点
this.sibling = null;

function App() {
  return (
    <div>
      i am
      <span>KaSong</span>
    </div>
  )
}
  • 作为静态工作单元:
// Fiber对应组件的类型 Function/Class/Host...
this.tag = tag;
// key属性
this.key = key;
// 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹
this.elementType = null;
// 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName
this.type = null;
// Fiber对应的真实DOM节点
this.stateNode = null;
  • 作为动态的工作单元:
// 保存本次更新造成的状态改变相关信息
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;

this.mode = mode;

// 保存本次更新会造成的DOM操作
/**
 * 形成effectList链表, 递归调用所有需要改变的组件
 */
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;

// 调度优先级相关
this.lanes = NoLanes;
this.childLanes = NoLanes;

Fiber工作原理

  • Fiber节点通过stateNode保存对应的dom
  • Fiber节点构成的fiber树, 对应dom树
  • 更新dom时, 使用双缓存技术: 在内存中构建并直接替换的技术
    • 当前帧计算绘制量比较大的时候, 清除上一帧, 到绘制当前帧会有空白
    • 直接在内存绘制当前帧的动画, 绘制完成后直接替换. 不会有空白
    • 对应着DOM树的创建和更新
  • 双缓存fiber树
    • 屏幕上对应的fiber树为current fiber树, 内存中对应的fiber树为workInProcess fiber树
    • current fiberworkInProcess fiber 通过alternate链接
    • wipFiber构建完成后, 交给renderer渲染, 渲染完成后, current指向wipFiber
    • 每次状态更新, 都会产生新的wipFiber树, 通过currentwipFiber树的替换, 完成DOM更新

mount时

  1. 首次执行ReactDOM.render创建FiberRootNode(源码中称为fiberRoot), 和rootFiber
    1. fiberRootNode为整个应用的根节点, rootFiber为当前render函数中传入组件的根节点
    2. current指向的rootFiber没有任何子fiber节点, current fiber树为空.
  2. render阶段, 根据组件返回的jsx, 在内存中以此创建fiber节点, 并链接在一起构成fiber树, 称为workInProcess fiber树
    1. 构建workInProcess fiber树时, 会尝试复用current fiber树中已有的fiber节点的内在属性
    2. 首屏渲染时只有rootFiber存在对应的current fiber. rootFiber.alternate => current rootFiber
  3. 构建完成的workInProcess fiber树, 在commit阶段渲染到页面上
    1. 此时dom树, 就变为了workInProcess fiber树对应的样子
    2. fiberRootNode更改current指针, 到workInProcess fiber
    3. workInProcess fiber树变为了current fiber

update时

  1. 我们点击p节点, 触发状态更新, 开启一次新的render阶段, 并构建一颗新的workInProcess fiber树
    1. workInProcess fiber的创建, 可以复用current fiber树对应的数据节点
    2. 是否复用的过程就是diff算法
  2. workInProcess Fiber树render阶段完成构建后进入到commit阶段渲染到页面上
    1. 渲染完毕后workInProcess Fiber树变为current Fiber树
  3. Fiber树在构建和替换的过程中, 完成dom的更新操作

JSX

  • jsx在编译时, 会被babel编译为React.createElement
  • ReactElement工作后, 会返回一个ReactElement, 并使用$$typeof进行标记
  • 所有jsx运行后的返回结果都是ReactElement.
  • ClassComponent对应的Element中的type字段为AppClass本身
  • FunctionComponent对应的Element中的type字段为AppFunc本身
  • React通过ClassComponent.prototype.isReactComponent = {};判断是否为ClassComponent
  • mount时, Reconciler根据jsx描述的组件内容, 组成组件对应的fiber节点
  • update时, Reconciler将jsx与Fiber节点保存的数据, 进行对比, 生成组件对应的节点. 并根据对比结果为Fiber节点打上tag