react 总结

76 阅读7分钟

hooks

类组件和hooks 中的state保存位置

他们的状态保存在组件实例对象FiberNode的memoizedState中,类组件是直接将state属性保存在这个memoizedState属性中,而hooks中,是用链表来保存状态的,memoizedState属性保存的其实是这个链表的头指针(head),通过wokinProgrssHooks 来保存当前的hooks,然后通过next 串联下一个hook,形成一个hooks的单向链表

链表中的每个节点其实是一个对象,正因为是一个链表,所以hooks才只能放在函数组件的最顶层,因为我们只能通过hooks调用顺序来与实际保存的数据关联

type Hook = {
	memoizedState: any; // 最新的状态值 (useEffect这里会是一个链表,来保存一个个的effect对象, useRef 保存的是ref对象,useMemo 保存的是缓存的值和deps )
	baseState: any; // 初使的状态值
	baseUpdate: Update<any, any> | null;
	next: Hook | null; // 指向下一个链表节点,下一个Hooks对象
	queue: UpdateQueue<any, any> | null; // 是一个保存对状态值操作的链表
}

useEffect 也是以链表的形式挂在hooks单项列表中

对于useEffect来说,会保存自身的每次effect对象,通过单向循环列表的方式保存在Hook的memoizedState 上面

更新状态

当我们更新hooks的状态的时候,调用dispatcher 的时候(相当于useState中的setState),并不会立即对状态值进行修改,而是创建一条修改操作记录,将其保存在Hook对象的queue属性上,在queue保存的链表中加上一个新的节点,当下次执行组件再次调用dispatcher的时候,就会根据每个Hook上的queue来计算最新的状态值

useEffect

​ mount 阶段

当我们调用useEffect的时候,组件第一次渲染就会调用mountEffect方法,生成一个effect对象,然后将他保存到Hook对象的memoizedState中

​ 根据函数组件函数体中依次调用useEffect 语句,然后构建一个链表并挂载到FiberNode 的updateQueue中,链表节点数据结构为

Effect = {
 tag, // 用来判断依赖项有没有变动 没有变动就是 NoHookEffect
 create, // 使用useEffect传入的函数体
 destroy, // 函数体执行后生成的用来清除副作用的函数
 deps, // 依赖项列表
 next: (null: any),
}

组建渲染完后,遍历链表来执行 (具体看执行阶段)

Update 阶段

​ 依次调用useEffect语句的时候,判断此时传入的依赖列表,与链表节点中的Effect.deps中保存的是否一致,如果一致,则给tag 标记为NoHookEffect,如果不一致,就更新efect对象,然后赋值给hook.memoizedState

执行阶段

遍历列表,如果遇到Effect.tag 为NoHookEffect 的节点就跳过,如果destroy是函数类型,就执行这个清除副作用的函数,执行create,并且执行结果

保存到destroy中,如果useEffect没有配置return ,则destroy就是undefined,

实际执行的时候,会先清楚上一轮的副作用,然后再执行本轮的effect

事件系统

  • 事件绑定

    当他执行解析虚拟do m对象的时候,会查看prop 是不是一个事件类型,如果是的话,就去检查这个react 事件依赖了哪些原生事件类型,然后判断一下这些原生的事件类型有没有被注册到documet上面,如果有的话就忽略,没有的话就注册到document(container)上, 回调为react 提供的dispatchEvent函数

  • 事件触发

    事件触发执行dispatchEvent函数,通过原生事件类型决定使用哪个合成事件类型。用他的实例,然后作为这次派发事件的事件对象,从点击的原生事件中找到对应的dom节点,然后从dom节点中找到一个最近的react组件实例,然后找到一条由这个实例节点不断向上组成的链,就是我们要触发合成事件的链

    然后反向触发这条链,父元素到子元素,模拟捕获阶段,触发onClickCapture事件

    然后正向触发这条链,子元素到父元素,模拟冒泡阶段,触发onClick事件

在将虚拟dom的事件挂载到真实dom的时候,进行了事件代理,将要挂载的事件进行处理,不管给哪一个dom绑定都统一代理到container上去,然后将event 事件进行包装,可以做兼容处理,兼融不同的浏览器,这样的话,也可以在事件处理函数之前和之后做一些事情,比如react 事件的批量更新。这样就不用频繁更新组件,比如setstate 就有时候是异步,有时候是同步(事件处理函数。生命周期等,就只要归react管就是异步,不归react管就是同步)(他是如何批量更新的,就是设置一个批量更新的标志,然后存储每个组件的updaters。 当组件是批量更新的时候,就将state放到任务队列中存储起来,当标志变为false的时候,直接合并state队列,然后更新组件)

Fiber

stack Reconciler ====> Fiber Reconciler

在react 加载或者更新组件树的时候,会做很多事情,比如调用组件生命周期,计算对比虚拟dom,更新dom树,他会同步进行,直到完成,但是当组件树比较庞大的时候,就会有问题,当用户进行ui交互的时候,react在更新组件,所以就会出现界面卡顿的状态,所以,React 利用Fiber 把一个耗时长的任务分成许多小片完成,每执行完一段过程,就把控制权交给React 负责任务协调的模块,如果有其他紧急就去做紧急任务,没有的话,就继续更新,主要利用了requestIdleCallback,

  1. requestAnimationFrame在重新渲染屏幕之前执行,非常适合用来做动画。
  2. requestIdleCallback在渲染屏幕之后执行,并且是否有空执行要看浏览器的调度,如果你一定要它在某个时间内执行,请使用 timeout参数。

前期的代码管理比较混乱,因为设计到不同的用户使用,而不同用户涉及的功能不一致,所以有了不同版本的代码。比如说对我们内部用户的微贷的版本,给行方使用的valid环境版本 (poc 版本),自运营版本,上架阿里云版本。 当时每个版本的进度不一致,包括功能不一致,(当时考虑到差异可能会越来越比较大)所以就把代码进行了分仓库管理,

但是现在慢慢的,实际遇到的是代码功能又开始了进行了同步,刚开始使用git 进行同步,这个功能改完去同步另一个仓库功能,始终比较麻烦,

但是今年比较固定了,只分为了两个版本,一个在aliyun准备上架的 , 一个valid 环境,所以就考虑把需要将自运营版本和valid版本代码进行合并,对代码需求进行兼容处理。当时我们使用valid环境做po c的时候,行方那边不能进行联网,所以一些静态资源就需要本地打包,不能使用c d n 引入,之前是手动替换c d n 资源,将资源全部放到本地,然后打包。然后就想着能不能搞一个valid 环境的打包方式,刚好在学习webpack , 就想到 利用we bpack 将打包的方式区分开,将静态资源进行区分打包,通过配置静态资源饮用路径,正常打包的时候不需要使用本地文件,valid 打包的时候使用,这样的话也可以减少包的体积

, (解决问题也是一个学习的过程),后面可以对项目进行一下性能优化,因为现在项目比如说有许多问题,引入的库不统一了,有的使用的版本比较旧,而且还夹杂着之前一些冗余的代码,比如说现在有个DX系统,使用起来就几个功能,但是还拖着一个大系统,最主要的是不怎么维护

  • 代码需求迭代必须规范,不能一个迭代图方便,多个同时进行的需求在一个迭代上,虽然是不同的模块
  • 持续学习,然后反哺业务