You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
letnextUnitOfWork;// 下一个执行单元functionworkLoop(deadline){while(nextUnitOfWork){nextUnitOfWork=performUnitWork(nextUnitOfWork)}}functionperformUnitWork(currentFiber){// TODO, 执行单元}requestIdleCallback(workLoop)
接下来继续完成 updateHostText 和 updateHostComponent 。 这两步需要进行 dom 的操作,所以先创建一个 dom.js 用来存放 dom 相关的操作。
// dom.js// 文本节点直接创建 textNode,host 节点创建 element 之后再进行属性的赋值。exportfunctioncreateDOM(currentFiber){if(currentFiber.elementType===ELEMENT_TEXT){returndocument.createTextNode(currentFiber.pendingProps.text)}conststateNode=document.createElement(currentFiber.elementType)setProps(stateNode,{},currentFiber.pendingProps)returnstateNode}// 除了 children 属性,其他的都作为 dom 的 AttributeexportfunctionsetProps(elem,oldProps,newProps){for(letkeyinoldProps){if(key!=="children"){if(newProps.hasOwnProperty(key)){setProp(elem,key,newProps[key]);}else{elem.removeAttribute(key);}}}for(letkeyinnewProps){if(key!=="children"){setProp(elem,key,newProps[key]);}}}functionsetProp(dom,key,value){if(/^on/.test(key)){dom[key.toLowerCase()]=value;}elseif(key==="style"){if(value){for(letstyleNameinvalue){if(value.hasOwnProperty(styleName)){dom.style[styleName]=value[styleName];}}}}else{dom.setAttribute(key,value);}returndom;}
关于 dom 操作就不多说了,这应该是基础,不算是 react 的核心。 updateHostText 和 updateHostComponent 的代码也不复杂,如下:
从 0 写一个 React(一)
在阅读这篇文章之前,我希望你已经了解过 React 的 Fiber 架构,如果还不熟悉,请阅读我的这篇:Deep In React 之浅谈 React Fiber 架构(一)。
准备工作
在环境搭建上我选择了 Parcel,因为它使用起来非常的简洁,配置少,使用起来方便。
首先通过 npm 安装 Parcel:
创建一个项目目录并且初始化 package.json 文件:
接下来创建 index.html 和 index.js,在 index.html 里引入 index.js
了解 jsx 并实现虚拟 DOM
jsx 的本质
这样的一段 jsx 代码其实对于浏览器来说是一段不合法的 js 代码,本质上,jsx 是 js 的语法糖,比如上面的这段代码会被 babel 转成如下代码:
可以看出来转化的逻辑大概是这样:
实现 React.createElement
清楚了 babel 的转化逻辑,接下来就来实现以下吧。
babel 配置
首先配置一下 .babelrc:
接下来在 index.js 里写一行代码看是否成功。
然后让项目跑起来:
然后访问 localhost:1234 就可以看到屏幕输出了
前端桃园
了。createElement
我们知道在 React 里,children 是作为 props 里面的一个属性,这根 jsx 转化出来的不一样。知道了 babel 转化 jsx 的规则,我们要实现 createElement 就非常的简单了,只需要利用 ES6 的 rest 参数,就可以非常容易的拿到所有的 children。
接下来在进行调试一下:
输出的结果如下,是符合我们的期望的。

实际上这个输出出来的,通过 createElement 方法返回的对象记录了这个 DOM 节点我们需要的信息,这个对象就被称为虚拟DOM。
初次渲染
在了解 fiber 架构之后,你就应该知道 fiber 是如何工作的,在初次渲染的时候:
第一步生成虚拟 DOM 上面已经完成了,接下来了解如何通过并发模式来生成 Fiber。
并发模式
理想情况下,我们应该把
render
拆成更细分的单元,每完成一个单元的工作,允许浏览器打断渲染响应更高优先级的工作,这个过程称为"并发模式(Concurrent Mode)"。这里用 requestIdleCallback 这个浏览器 API 来实现,这个 API 可以在线程空闲的时候去执行回调函数(执行我们的工作单元)。
大致的代码如下:
全局遍历
nextUnitOfWork
为下一个执行单元,是一个 Fiber 结构。我们要知道架构改为 fiber 的一个大的特征就是将结构改为了链表,链表的遍历就是一个一个的,
performUnitWork
函数就是执行当前的 Fiber,然后返回下一个 Fiber,这样遍历整棵树。但是目前的代码是有问题的,因为没有被打断的逻辑,那咱们再加上被打断的逻辑。
打断的逻辑就在
shouldYield = deadline.timeRemaining() < 1
这行代码里,如果时间片小于 1 毫秒,就被打断,等待浏览器下次空闲的时候再执行。有没有忽然觉得如此高大上的概念(并发模式),其实原理很简单。
合理拆分文件
为了便于理解,现在将文件进行拆分一下,将
React.xxx
的 API 放到react.js
里。另外我们都知道 react 要进行渲染需要有个
render
函数,这个是在 ReactDOM 下面的 API,所以再建一个react-dom.js
用来放render
函数。对于刚才我们所写的并发模式相关的代码,放到
schedule.js
里。另外再增加一个
constants.js
的常量文件,用来存放一些特殊常量。所以现在就有 6 个文件,
index.html
、index.js
、react.js
、react-dom.js
、schedule.js
、constants.js
。index.html 里需要添加一个 react 挂载的节点。
index.js 需要导入
React
和ReactDOM
,然后调用render
函数进行渲染。将
createElement
放到 react.js 里,进行简单的改造,并且创建constants.js
。改造的点主要是针对文本节点,如果是文本节点的时候返回一个跟正常的虚拟 DOM 节点一样的结构,而不是直接返回文本,这样做的目的是为了后面方便统一处理。
constants.js
里存放着节点的一些类型。react-dom.js
的 render 函数写成这样:新建一个 rootFiber 的 fiber,然后通过
scheduleRoot
进行去调度。schedule.js
目前就是这样:scheduleRoot
所要做的事情就是将nextUnitOfWork
赋值为rootFiber
,这样requestIdleCallback
调用的时候workLoop
里才有值。构建 fiber list
遍历整棵树
**performUnitOfWork**
是如何去遍历整棵树的逻辑的函数,同时也会返回下一个要完成的 fiber。Fiber 架构遍历是采用的深度优先遍历,会先遍历子节点,如果子节点没有,再遍历兄弟节点,如果没有兄弟节点,就返回到父节点。
所以 performUnitOfWork 的代码如下:
Fiber 的结构
如果你是了解 fiber 架构的,那么对于 Fiber 是这么一个结构应该不陌生。其中
tag
和effectTag
放在constans.js
里,具体的常量的值我这里保持跟 React 里一样,首次更新的也不多,所以constans.js
增加的常量有:将子元素变为子 Fiber
将子元素变为 fiber,首先需要判断当前 fiber 的 tag 类型,不同的类型有不同的策略。
接下来就是重点了,要实现一个
reconcileChildren
的函数,这个函数理论上就是 diff 的过程,但是由于首次渲染,没有 diff 的过程,就直接创建 fiber 了。咱们先写根节点的时候的更新方法(updateHostRoot)吧。
接下来实现以下
reconcileChildren
这个函数。执行完
reconcileChildren
之后,所有的子节点都转化为了 fiber,不过还有一些属性没有添加上去,比如stateNode
和nextEffect
。接下来继续完成
updateHostText
和updateHostComponent
。这两步需要进行 dom 的操作,所以先创建一个
dom.js
用来存放 dom 相关的操作。关于 dom 操作就不多说了,这应该是基础,不算是 react 的核心。
updateHostText
和updateHostComponent
的代码也不复杂,如下:到这个时候,fiber list 基本构建完毕,如果在
updateHostRoot
的最后一行打印一下currentFiber
应该就可以看到整个构建的 fiber 链表。接下来就是完成 effectList 的构建。
构建 effect list
effect list 是在
completeUnitOfWork
函数里完成的,具体代码如下:commit effect list
构建完 effect list 了就可以开始 commit 了,构建完 effect list 的时机就是没有 nextUnitOfWork 了,就代表已经调和完毕了,到了下一个阶段:commit。
那么在
workLoop
就会有一个判断是否存在下一个执行单元,如果没有就进行提交阶段。我们在提交的时候就要拿到整颗 fiber 链表的头结点,但是之前的
nextUnitOfWork
已经为空了,所以还需要一个变量来存储当前正在渲染的根 fiber,这个 fiber 就是之前学到的WorkInProgress Tree
。所以就需要一个变量:
workInProgressRoot
的遍历用来存储当前渲染的 fiber 树,并且在scheduleRoot
的时候把根 fiber 赋值给它所以
commitRoot
就应该是这样:到此,就已经可以渲染出这样的效果了:

撒花,结束,接下来将实现元素的更新以及函数式组件,还有 hooks。
demo 代码在这里:https://github.com/crazylxr/luffy/tree/chapter1
参考资料
珠峰架构公开课
The text was updated successfully, but these errors were encountered: