Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react相关 #34

Open
BenFeiJr opened this issue Apr 21, 2023 · 11 comments
Open

react相关 #34

BenFeiJr opened this issue Apr 21, 2023 · 11 comments

Comments

@BenFeiJr
Copy link
Owner

BenFeiJr commented Apr 21, 2023

调度:

1、大致流程
我们可以将react的运行流程大致分为输入与输出,输入包括首次渲染或者其他更新setState等等,输出就是指将生成的dom插入到页面中;cocurrent模式下,当输入发生时,输入会被包裹成一个task,放进taskqueue 或者 timer queue种被schuler进行消费;

为什么cocurrent?为了不让react长时间阻塞浏览器线程,造成页面卡顿或者页面假死。

怎么做,输入包裹成task,让schuler异步消费,schuler设定最大执行时间,每次执行完一个task,执行下一个task前判断是否超过最大时间,超过则取消task循环调用,让出线程。当然每个task也有可能执行时间过长,那么task内部也要进行判断;让出后,立刻注册下一回event loop的调用;

如何实现下一回的自动调用呢?使用了message channel,会在宏任务时机让浏览器调用,浏览器也会对调用时机做一些优化。
为什么不用setTimeout呢?
为什么不用RAF呢?RAF的执行时机是微任务,在一个event loop中,微任务会一直执行,不能起到让出线程的效果
为什么不用requestIdleCallback呢?因为这个方法受限于浏览器刷新频率的限制,调用频率不能保证。

既然维护了队列循环执行,那么就得有优先级,保证高优先级任务先执行。

schuler消费时机,是监听message channel的回调,

task queue 和 timer queue是小顶堆,保证每次

1、为什么用小顶堆,不用数组sort
因为task queue 是动态的,时刻在插入和推出,每次这样操作后都要重新调整数组内item的排序,而小顶堆的时间复杂度是logn,
sort的复杂度是nlogn

https://cloud.tencent.com/developer/article/2109572
https://www.jianshu.com/p/15a29c0ace73

https://www.qinguanghui.com/articles/react-concurrency
https://juejin.cn/post/6914089940649246734
https://github.com/xitu/gold-miner/blob/master/TODO1/scheduling-in-react.md
https://juejin.cn/post/6889314677528985614

http://www.paradeto.com/2020/12/30/react-concurrent-1/

@BenFeiJr BenFeiJr changed the title react调度 react相关 Apr 25, 2023
@BenFeiJr
Copy link
Owner Author

begin work & complete work

@BenFeiJr
Copy link
Owner Author

commit

@BenFeiJr
Copy link
Owner Author

BenFeiJr commented Apr 25, 2023

event
react 16事件系统
合成事件,抹平差异,提供统一的事件注册,比如onChange可能由原生的几个其他事件组成,但是对外暴露出来只是一个onChange

委托在document,所以大概流程是,点击click,原生事件由顶到目标元素捕获阶段,目标元素阶段,目标元素往顶冒泡阶段,冒泡到document,document拿到触发事件的dom,从dom上获取到react实例,找到实例的所有祖先元素,模拟一遍捕获阶段,目标元素,模拟一遍冒泡阶段;

问题,两个父子reactDom.render(parent,) render(child),child取消冒泡也没用,还是会冒泡到parent

react 17事件系统
不在委托在document上,委托在root元素上
在 React17 中,对 React 应用根 DOM 容器的事件委托分别在捕获阶段和冒泡阶段。即:

当根容器接收到捕获事件时,先触发一次 React 事件的捕获阶段,然后再执行原生事件的捕获传播。所以 React 事件的捕获阶段调用e.stopPropagation()能阻止原生事件的传播。
当根容器接受到冒泡事件时,会触发一次 React 事件的冒泡阶段,此时原生事件的冒泡传播已经传播到根了,所以 React 事件的冒泡阶段调用e.stopPropagation()不能阻止原生事件向根容器的传播,但是能阻止根容器到页面顶层的传播。

https://musicfe.com/react-event/
http://www.ayqy.net/blog/react-17/
https://zhuanlan.zhihu.com/p/583059579

@BenFeiJr
Copy link
Owner Author

BenFeiJr commented Apr 25, 2023

@BenFeiJr
Copy link
Owner Author

BenFeiJr commented Apr 25, 2023

fiber

react15在render阶段的reconcile是不可打断的,这会在进行大量节点的reconcile时可能产生卡顿,因为浏览器所有的时间都交给了js执行,并且js的执行时单线程。为此react16之后就有了scheduler进行时间片的调度,给每个task(工作单元)一定的时间,如果在这个时间内没执行完,也要交出执行权给浏览器进行绘制和重排,所以异步可中断的更新需要一定的数据结构在内存中来保存工作单元的信息,这个数据结构就是Fiber。

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  //作为静态的数据结构 保存节点的信息 
  this.tag = tag;//对应组件的类型
  this.key = key;//key属性
  this.elementType = null;//元素类型
  this.type = null;//func或者class
  this.stateNode = null;//真实dom节点

  //作为fiber数架构 连接成fiber树
  this.return = null;//指向父节点
  this.child = null;//指向child
  this.sibling = null;//指向兄弟节点
  this.index = 0;

  this.ref = null;

  //用作为工作单元 来计算state
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;
    
	//effect相关
  this.effectTag = NoEffect;
  this.nextEffect = null;
  this.firstEffect = null;
  this.lastEffect = null;

  //优先级相关的属性
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  //current和workInProgress的指针
  this.alternate = null;

@BenFeiJr
Copy link
Owner Author

BenFeiJr commented Jul 7, 2023

virtual dom

virtual dom是js对象来描述dom的结构和信息,由react.createElement生成

大量的dom操作慢,很小的更新都有可能引起页面的重新排列,js对象优于在内存中,处理起来更快,可以通过diff算法比较新老virtual Dom的差异,并且批量、异步、最小化的执行dom的变更,以提高性能

另外就是可以跨平台,jsx --> ReactElement对象 --> 真实节点,有中间层的存在,就可以在操作真实节点之前进行对应的处理,处理的结果反映到真实节点上,这个真实节点可以是浏览器环境,也可以是Native环境

virtual Dom真的快吗?其实virtual Dom只是在更新的时候快,在应用初始的时候不一定快

​ React.createElement的源码中做了如下几件事

处理config,把除了保留属性外的其他config赋值给props
把children处理后赋值给props.children
处理defaultProps
调用ReactElement返回一个jsx对象(virtual-dom)

结构:

{
    $$typeof: REACT_ELEMENT_TYPE,//表示是ReactElement类型
    type: type,//class或function
    key: key,//key
    ref: ref,//ref属性
    props: props,//props
    _owner: owner,
  };

@BenFeiJr
Copy link
Owner Author

BenFeiJr commented Jul 7, 2023

生命周期

WX20190524-200633@2x

@BenFeiJr
Copy link
Owner Author

BenFeiJr commented Jul 7, 2023

react为什么要升级

为什么

因为老的react架构方式,一旦reconciler开始就只能执行到结束,又因为js单线程,所以此过程中如果有其他任务进来就得一直等待,造成卡顿

新的架构,希望reconciler可以中断

react的几个重大更新

v0.14 => v15
version命名变更

v15.4
React 和 ReactDOM包分离

v16
unstable_handleError => componentDidCatch
一些生命周期的变动:https://legacy.reactjs.org/blog/2017/09/26/react-v16.0.html

v16.2
新增 React.Fragment

v16.3
新增生命周期:getDerivedStateFromProps 和 getSnapshotBeforeUpdate
新增context api: React.createContext

v16.6
新增:React.memo 和 React.lazy 和 contextType
https://legacy.reactjs.org/blog/2018/10/23/react-v-16-6.html

v16.8
新增:hooks
https://legacy.reactjs.org/blog/2019/02/06/react-v16.8.0.html

v16.9
生命周期 unsafe
https://legacy.reactjs.org/blog/2019/08/08/react-v16.9.0.html

v17
事件委托不再document,而是root dom container
https://legacy.reactjs.org/blog/2020/10/20/react-v17.html

v18
并发 Concurrent
https://zh-hans.react.dev/blog/2022/03/29/react-v18

@BenFeiJr
Copy link
Owner Author

BenFeiJr commented Jul 7, 2023

react工作流程概览

触发更新(初次/更新)

ReactDOM.render

有三种入口模式:
legacy 模式: ReactDOM.render(, rootNode)。这是当前 React app 使用的方式。当前没有计划删除本模式,但是这个模式可能不支持这些新功能。
blocking 模式: ReactDOM.createBlockingRoot(rootNode).render()。目前正在实验中。作为迁移到 concurrent 模式的第一个步骤。
concurrent 模式: ReactDOM.createRoot(rootNode).render()。目前在实验中,未来稳定之后,打算作为 React 的默认开发模式。这个模式开启了所有的新功能。
legacy模式是我们常用的,它构建dom的过程是同步的,所以在render的reconciler中,如果diff的过程特别耗时,那么导致的结果就是js一直阻塞高优先级的任务(例如用户的点击事件),表现为页面的卡顿,无法响应。
concurrent Mode是react未来的模式,它用时间片调度实现了异步可中断的任务,根据设备性能的不同,时间片的长度也不一样,在每个时间片中,如果任务到了过期时间,就会主动让出线程给高优先级的任务。这部分将在第15节 scheduler&lane模型 。

this.setState
setState为什么是异步的?facebook/react#11527 (comment)

this.forceUpdate
useState
useReducer

render阶段

产生的更新包装成task,(task长啥样)

var newTask = {
id: taskIdCounter++, // id: 一个自增编号
callback, // callback: 传入的回调函数
priorityLevel, // priorityLevel: 优先级等级
startTime, // startTime: 创建task时的当前时间
expirationTime, // expirationTime: task的过期时间, 优先级越高 expirationTime = startTime + timeout(优先级转换出来) 越小;
sortIndex: -1,
};
newTask.sortIndex = expirationTime; // sortIndex: 排序索引, 全等于过期时间. 保证过期时间越小, 越紧急的任务排在最前面

push进taskQueue 或者 timerQueue 等待被schuler消费(调度逻辑)

如果创建task的方法传入的options里面有延时参数,则startTime = current + 延时,任务放进timerQueue,虽有这块逻辑,但是没有用到,所以都是放进taskQueue的


取最高优先级task,处理task,(优先级逻辑)

task的过期时间

reconciler,(做了些什么)生成wip树(一个task执行完成,或者单个task执行时间过长,都会判断超时)

从rootFiber开始,深度优先遍历,执行beginWork (创建或者复用fiber)和 completeWork(处理fiber的props、创建dom、创建effectList),构建fiber树(更新时涉及到diff算法),
diff算法:https://xiaochen1024.com/courseware/60b1b2f6cf10a4003b634718/60b1b354cf10a4003b634721
单节点diff:
key && type相同,则复用
key || type不同,删除重建
多节点diff:

commit阶段

渲染界面,wip 替换 current tree

循环effectList队列

@BenFeiJr
Copy link
Owner Author

状态管理解决方案

yinguangyao/blog#56

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant