React源码解析18(3)------ beginWork的工作流程【mount】

news/2024/7/15 18:52:51 标签: react.js, 前端, 前端框架

摘要

OK,经过上一篇文章。我们调用了:

const root = document.querySelector('#root');
ReactDOM.createRoot(root)

生成了FilberRootNode和HostRootFilber。
并且二者之间的对应关系也已经确定。

而下一步我们就需要调用render方法来讲react元素挂载在root上:

//第一节实现的jsx方法
const reactElement = jsx("div", {
  ref: "123",
  children: jsx("span", {
    children: "456"
  })
});
ReactDOM.createRoot(root).render(reactElement)

所以我们现在要实现ReactDOM.createRoot中返回的render方法。

1.update和updateQueue

首先我们思考一下,在React中,除了通过上面的render方法,会让React组件更新。还有setState等逻辑,也可以触发React的更新。
也就是说,我们要实现一个方法。在触发React组件更新时调用:updateContainer。

在实现updateContainer方法前,我们先实现一套机制,用来保存更新的内容。这里可以创建一个update.js用来写这部分内容:


//创建一个更新内容
function createUpdate(action) {
  return {
    action
  }
}

//给FilberNode创建一个更新队列
function createUpdateQueue() {
  return {
    shared: {
      pending: null
    }
  }
}

//在更新队列里添加更新内容
function enqueueUpdate(updateQueue, update) {
  updateQueue.shared.pending = update
}

//根据更新的内容,去更新FilberNode(this.setState)
function processUpdateQueue(baseState, pendingUpdate) {
  const result = {
    memoizedState: baseState
  }
  if(pendingUpdate !== null){
    const action = pendingUpdate.action;
    //setState(() => {}) 传入方法
    if(typeof action === 'function'){
      result.memoizedState = action(baseState);
    }else {
      //setState()
      result.memoizedState = action;
    }
  }
  return result;
}

这个时候,我们的更新相关的方法已经准备好了。现在就要开始干了。首先要在FilberNode上增加一个属性,updateQueue用来保存更新的内容:
this.updateQueue = null;

2.updateContainer方法

现在我们开始实现updateContainer方法,该方法接受两个参数,第一个是通过createContainer方法创建出来的FilberRootNode,第二个就是render方法传入的ReactElement。

function createRoot(root) {
  const filberRootNode = createContainer(root);
  return {
    render(element) {

    }
  }
}

function updateContainer(root, element) {
  
}

在方法里,我们思考一下,如果不考虑setState的情况。第一次渲染的时候,对于最外层的FilberNode,他需要更新的内容,不就是传入的element吗。

所以我们通过root.current拿到最外层的FilberNode。执行对应的更新操作:

function createRoot(root) {
  const filberRootNode = createContainer(root);
  return {
    render(element) {
      updateContainer(filberRootNode, element)
    }
  }
}

function updateContainer(root, element) {
  const hostRootFilber = root.current;
  const update = createUpdate(element);
  hostRootFilber.updateQueue = createUpdateQueue()
  enqueueUpdate(hostRootFilber.updateQueue, update);
  console.log(hostRootFilber)
}

我们将对应的节点打印出来看一下,最外层的FilberNode此时已经有了updateQueue,并且里面的内容就是对应的ReactElement
在这里插入图片描述

3.实现beginWork

OK,现在我们最外层的FilberNode已经准备好,我们开始准备构建Filber树。其实构建Filber树的过程,就是创建好所有的FilberNode,并且通过return,sibling,child这三个属性进行构建。

而表示整棵树的结构,都存在updateQueue中的ReactElement,我们就是要通过它去创建这颗Filber树。

现在我们不考虑有sibling属性的情况,只考虑有return和child属性的情况,创建beginWork方法:

function beginWork(nowFilberNode) {

}

function updateContainer(root, element) {
  const hostRootFilber = root.current;
  const update = createUpdate(element);
  hostRootFilber.updateQueue = createUpdateQueue()
  enqueueUpdate(hostRootFilber.updateQueue, update);
  beginWork(hostRootFilber);
}

我们主要要做的就是,在beginWork里面,创建好所有的fiberNode。并且找清楚他们之间的对应关系。所以我们的beginWork一定是一个递归的方法:
我们会判断当前filberNode的tag:

function beginWork(nowFilberNode) {
  switch (nowFilberNode) {
    case HostRoot: {
		return updateHostRoot(nowFilberNode); 
    }
    case HostComponent: {

    }
    case HostText: {

    }
    case FunctionComponent: {

    }
    default: {
      console.error('错误的类型')
    }
  }
}

由于第一次调用,传入的是最外面的filberNode,所以tag应该为HostRoot。我们针对于这种情况写一个方法updateHostRoot。

function updateHostRoot(filberNode) {
  const baseState = filberNode.memoizedState;
  const updateQueue = filberNode.updateQueue;
  const pending = updateQueue.shared.pending;
  updateQueue.shared.pending = null;
  const { memoizedState } = processUpdateQueue(baseState, pending);
  filberNode.memoizedProps = memoizedState;
  const nextChildren = filberNode.memoizedProps;
  console.log(nextChildren);
  console.log(filberNode);
}

对于首屏渲染,我们知道对于FilberNode来说,更新的内容就是他的子节点。所以我们更新好FilebrNode的updateQueue属性和memoizedState,memoizedProps属性后。

可以直接拿到它的子节点nextChildren,不过这个节点是ReactElement类型,我们要将它转换成FilberNode,所以我们还需要一个方法:reconcilerChildren。

function reconcileChildren(element) {
  let tag;
  if(element.$$typeof === REACT_ELEMENT_TYPE){
    tag = HostComponent;
  }else if(typeof element === 'string'){

  }
  return new FilberNode(tag, element.props, element.key)

}

我们创建好的子FilberNode,用element的props初始化FilberNode的penddingProps。
这个时候我们在updateHostRoot中调用该方法,并将子FilberNode打印出来。

function updateHostRoot(filberNode) {
  /**
  * 其他代码
  **/
  const newFilberNode = reconcileChildren(nextChildren);
  filberNode.child = newFilberNode;
  newFilberNode.return = filberNode
  console.log(newFilberNode);
  beginWork(newFilberNode)
}

可以看到子FilberNode和父节点的关系已经更新好,同时也将自己的ReactElement放在了pendingProps里。

在这里插入图片描述

Ok,对于HostRoot类型(最外层的FilberNode),我们有了updateHostRoot方法处理,那对于HostComponent类型,我们自然也需要updateHostComponent方法:

function updateHostComponent(filberNode) {
  const nextChildren = filberNode.pendingProps.children;
  const newFilberNode = reconcileChildren(nextChildren);
  filberNode.child = newFilberNode;
  newFilberNode.return = filberNode;
  beginWork(newFilberNode)
}

同样的,对于文本类型的节点,自然也不需要去给它创建FilberNode。这里面我们不做处理就好。

到这里,我们就已经简单的处理了只有child和return属性的Filber树,最终也可以打印出来这颗树的样子:
在这里插入图片描述
可以看到每个FilberNode都具有child和return两个属性。


http://www.niftyadmin.cn/n/4929088.html

相关文章

java调用第三方接口工具类 (HttpClientUtils.java)

1. 依赖 <!--httpclient--> <dependency><groupId>commons-httpclient</groupId><artifactId>commons-httpclient</artifactId><version>3.1</version> </dependency><!-- 阿里JSON解析器 --> <dependency>…

【云原生】K8S集群

目录 一、调度约束1.1 POT的创建过程1.1调度过程 二、指定节点调度2.1 通过标签选择节点 三、亲和性3.1requiredDuringSchedulingIgnoredDuringExecution&#xff1a;硬策略3.1 preferredDuringSchedulingIgnoredDuringExecution&#xff1a;软策略3.3Pod亲和性与反亲和性3.4使…

竞赛项目 深度学习的智能中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

【创建型设计模式】C#设计模式之原型模式

原型模式是一种创建型设计模式&#xff0c;它通过复制现有对象来创建新对象&#xff0c;而无需通过实例化的方式。它允许我们使用已经存在的对象作为蓝本&#xff0c;从而创建新的对象&#xff0c;这样可以避免重复初始化相似的对象&#xff0c;提高了对象的创建效率。 现在给…

游戏行业实战案例 4 :在线时长分析

【面试题】某游戏数据后台设有「登录日志」和「登出日志」两张表。 「登录日志」记录各玩家的登录时间和登录时的角色等级。 「登出日志」记录各玩家的登出时间和登出时的角色等级。 其中&#xff0c;「角色id」字段唯一识别玩家。 游戏开服前两天&#xff08; 2022-08-13 至 …

CEC2013(MATLAB):遗传算法(Genetic Algorithm,GA)求解CEC2013的28个函数

一、遗传算法GA 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;起源于对生物系统所进行的计算机模拟研究&#xff0c;是一种随机全局搜索优化方法&#xff0c;它模拟了自然选择和遗传中发生的复制、交叉(crossover)和变异(mutation)等现象&#xff0c;从任…

协程(一)单机--》并发--》协程

目录 一 协程的概述1.1 并行与并发1.2 线程1.3 新的思路1.4 Goroutine 二 第一个入门程序 一 协程的概述 我查看了网上的一些协程的资料&#xff0c;发现每个人对协程的概念都不一样&#xff0c;但是我认可的一种说法是&#xff1a;协程就是一种轻量级的线程框架&#xff08;K…

【深度学习】多粒度、多尺度、多源融合和多模态融合的区别

多粒度&#xff08;multiresolution&#xff09;和多尺度&#xff08;multiscale&#xff09; 多粒度&#xff08;multiresolution&#xff09;和多尺度&#xff08;multiscale&#xff09;都是指在不同的空间或时间尺度上对数据或信号进行分析和处理。其中 多尺度&#xff1…