从零实现react hooks

Posted lin-fighting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零实现react hooks相关的知识,希望对你有一定的参考价值。

diff dom

老知识点了,配置babel,对Jsx语法转换成React.createElement,
使用自己定义的createElement,


使用自己定义的createElement。

这个element的话Babel会转化成createElement,

虚拟dom就打印出来了。

实现初次渲染

思路

每个fiber有三个指针,return,child,sibling。顺序就是

代码实现

定义节点类型

实现render函数

render函数创建rootFiber节点,然后调用调度函数scheduleRoot函数。
react里面有好几个包,reconciler(协调) schedule(调度)

实现schedule

schedule有两个阶段,如图

这里的render函数并不是将虚拟dom转化成真实dom,而是根据虚拟dom生成fiber树,然后再收集effectList。
结合我们之前实现的fiber阶段和requestIdleCallback函数,来实现schedule函数完成调度功能。也就是我们熟悉的workLoop,performUnitOfWork这些函数的实现

定义两个变量指定根节点

利用requestIdleCallback执行workLoop函数,渲染fiber

每次进来都会判断当前是否还有空闲时间。没时间之后我就继续去请求requestIdleCallback函数,下次再执行该任务。
接着完成performUnitOfWork函数,该函数会返回下个该执行的任务

思路:对每个需要执行的任务调用BiginWork创建fiber,以根节点为例子,其会创建一个newFiber,然后将跟fiber的child指向该fiber,这样就可以顺理成章走第二步了。beginwork后的判断就是正常三步判断了,先判断儿子,没儿子了表明该任务完成了,该任务没弟弟,表明父fiber完成了(子fiber完成即认为完成)就直接返回父fiber继续走while循环,直到连currentFiber.return为null的时候,即根fiber完成了,就真正完成了。

beginwork函数实现


直接调用udateHostroot,我们先处理根节点的。

newchildren就是根节点了。
再调用reconcileChildren函数去处理根fiber和创建子fiber。

这个函数用来创建子fiber并创建联系,这样就可以通过currentFiber的child或者currentFiber.child.sibling这些来拿到哥哥弟弟等操作,只能建立一层联系,即父亲与儿子,孙子的联系是等到执行儿子的fiber时候才建立的。

当所有的完成后,看看completeUnitOfWork执行了什么
completeUnitOfWork主要是用来建立起一个单链表,使利用每个fIber的firstEffect,LastEffect以及NextEffect来建立一个单链表。

依照这个例子,最终的完成顺序应该是:
A1(text) => B1(text) => C1(text) => C1(div) => C2(text) => C2(div) => B1(div) => B2(text) => B2(div) =>A1(div)
文本节点,也是一个fiber。
全部代码:

//收集有副作用的fiber,组成effect list  每个fiber有两个属性,firstEffect指向第一个有副作用的子fiber 
//lastEffect 指向最后一个有副作用的子fiber 中间有副作用的用nextEffect做成一个单链表
// firstEffect(大儿子) firstEffec.nextEffect指向二儿子  firstEffec.nextEffect.nextEffect指向小儿子lastEffect(小儿子)
function completeUnitOfWork(currentFiber) {
  //将所有副作用挂载到根fiber上
  // 挂载到A1的顺序: C1 C2 B1 D1 D2 B2 A1
  let returnFiber = currentFiber.return;
  if (returnFiber) {
    //这一段是把自己儿子挂载到父亲上
    if (!returnFiber.firstEffect) {
      //A1 firstFIber=>C1
      returnFiber.firstEffect = currentFiber.firstEffect; // 到B1的时候 A1.firstEffect=>C1  
    }

    if (currentFiber.lastEffect) { //C2  D2
      if (returnFiber.lastEffect) { 
        // B1->D1
        returnFiber.lastEffect.nextEffect = currentFiber.firstEffect; //到B2的时候 A1.lastEffect(B1).netEffect=>D1 B1->D1
      }

      returnFiber.lastEffect = currentFiber.lastEffect;   // 到B1的时候 A1.lastFiber=>C2 到B2的时候 A1.lastEffect =>D2
    }

    //这一段是挂在完儿子后,挂载自己到父亲上。因为调用顺序就是完成顺序,当到B1的时候,C1 C2肯定执行过了。
    const effectTag = currentFiber.effectTag;
    if (effectTag) {
      //给底层人士用的C1 C2 B1 B2
      //如果父节点有lastFiber,那么此时先让lastFiber的下个节点指向currentFiber,再让lastFiber指向currentFiber(单链表结构)
      if (returnFiber.lastEffect) {
        // C2->B1
        returnFiber.lastEffect.nextEffect = currentFiber; //到B1的时候 C2=>A1 到B2的时候 D2.next=>B2
      } else {
        returnFiber.firstEffect = currentFiber;
      }
      // A1的last=>B1 完成C1->C2->B1,以此类推
      returnFiber.lastEffect = currentFiber; //到B1的时候 A1.lastEffect=>B1 到B2的时候 A1.last=>B2 (此时完成了C1,C2,B1,D1,D2,B2)
    }
  }
}

一步一步看

进来的第一个完成的肯定是A1text
A1text的父节点就是A1(div)
此时的A1text没有firstEffect或者lastEffect,就是说没有子节点,所以这一段不用注意,这一段是给有子节点的比如像B1,他必须将C1 C2先挂载上单链表后,才能挂载自己,

这段对A1 text来说就是让父节点 A1的firstEffect和LastEffect指向A1(text)
这样A1text就成功成为了单链表第一个节点。接着会继续通过firstEffect,lastEffect,nextEffect来建立每个联系,A1text会是第一个节点,A1(div)是最后一个上单链表的。
所以这段的作用主要就是让自己上单链表。
completeUnitOfWork的目的就是收集effect list,建立一个单链表。


逐步遍历链表上每个节点,然后执行commitWork函数。

在commitWork上,判断操作,是增加还是删除还是替换,目前只有增加,所以直接拿到retun父节点的stateNode直接执行appendChild即可。
比如对于A1text来说returnFIber就是A1(div),直接A1(div)后插入儿子即可。

第一步完成

小结:

react会通过workLoop结合requestIdleCallback来运行workLoop函数,workLoop会循环遍历执行performUnitOfWork函数,传入当前的节点,然乎返回下一次执行的节点。performUnitOfWork做了很多事情,首先BeginWOrk()方法为每个节点创建了fiber,并且建立了父亲与儿子的联系。其次,执行完beginwork的节点,当没有子节点后,(即子节点没有子节点或者弟弟节点的时候)就算完成,此时必须执行completeUnitOfWork函数来收集依赖,形成effect list。怎么收集呢,通过每个fiber的firstEffet lastEffect nextEffect来建立一个单链表结构,文本节点也是一个fiber哦,所以按道理第一个就是A1text,因为A1text没有子节点,所以其最先完成。所有fiber执行完completeUnitOfWork的时候,单链表已经完成,此时第一阶段完成,render阶段完成,此时到达提交阶段,必须执行commitRoot,这个方法会拿到根节点的第一个firstEffect,也就是A1text,然后循环遍历nextEffect,一个一个执行commitWork,判断其是增删改还是,做对应的操作。此时就算完成了第一个上dom树的步骤,

以上是关于从零实现react hooks的主要内容,如果未能解决你的问题,请参考以下文章

使用Hook更新上下文状态值

React 17 + Vite + ECharts 实现疫情数据可视化「03 学习 React Hooks」

React 17 + Vite + ECharts 实现疫情数据可视化「03 学习 React Hooks」

入门React 17 + Vite + ECharts 实现疫情数据可视化「03 学习 React Hooks」

入门React 17 + Vite + ECharts 实现疫情数据可视化「03 学习 React Hooks」

React Hooks 实现和由来以及解决的问题