An element is not an actual instance. Rather, it is a way to tell React what you want to see on the screen. You can’t call any methods on the element. It’s just an immutable description object with two fields: type: (string | Component) and props: Object.*
前言
之前自己看了些资料和代码,这里尝试通过实现一个类react的框架,来理解下react的工作流程,写文章也是对自己思路的一个整理,如有谬误还请大家帮忙指出。
主要内容
没有包含的内容
MiniReact
React Element
根据Dan在React Components, Elements, and Instances里的讲解,react element是指
简单来说,react element是我们要绘制的页面结构的javascript描述 举个例子,下方这样的页面结构
所对应的react element如下
可以看出react element是一个典型的树状结构。而React初次渲染的过程就是把react element转换为dom节点的过程,假设我们已经有了一个上面这样的react element对象,下面来看下ReactDom.render是如何把react element转换为dom树的。
ReactDom.render的实现
我们需要做的是遍历react element树来生成dom节点,对于树状结构最容易想到的遍历方式就是递归,于是有了下面的伪代码:
渲染函数有了,那react element又是如何生成的呢,我们知道在react里是通过jsx来描述react elements的,那来看下jsx到底做了哪些工作?
jsx与React Elements
下面是babel repl中的截图 可以看到babel其实是把jsx转换成了对React.createElement方法的调用 通过查看
@babel/plugin-transform-react-jsx
的说明,看到可以通过给jsx加注释的方式来自定义转换后的函数 现在只要我们实现了MiniReact.createElement方法,就可以直接在代码里通过jsx来描述react elements了 因为它做的工作只是返回一个javascript对象,所以实现起来还是比较简单到目前为止基本实现了,从『数据』到『dom节点』的初始渲染过程 那当数据更新时,我们可以重新生成新的elements,然后调用render生成新的dom树。再把root container的innerHTML改为新生成的dom树就完成了页面的更新。 但这样做有两个问题:
先来解决第一个问题,我们在update的过程中引入reconcile。
reconcile过程简介
于是引入一个新的数据结构instance:
把之前生成dom树的render函数重命名为instantiate, 返回值为instance类型
保存之前生成的instance对象
添加了reconcile之后发现,只有被影响到的节点会更新啦~, 那全局state的问题怎么解决呢,我们知道react 16之前只有react的类组件是可以有自己的state的,那现在我们来引入Component
Component与state
首先,我们需要有一个Component基类来供自定义组件继承
当在elements中引入自定义的Component后,意为着element.type可以是一个function, 而不再只能是dom节点的tagName, 我们来更改instatiate函数的实现
另外reconcile的过程也需要少许更改
至此,我们引入了component从而支持了局部的state, 页面现在可以进行部分刷新了~ 上面列举的内容,与React 16之前的结构还是基本类似的,React 16主要的不同是它引入了fiber架构,那啥是fiber呢?
Fiber简介
Fiber是React 16以后引入的新的reconciliation算法,它的名称来自于React实现中的Fiber数据组构。 引入Fiber的目的是实现增量渲染:
通俗的讲就是reconciliation的过程在16之后是可中断/暂停/继续的,它带来的优势主要是渲染任务现在支持区分优先级了。e.g.像用户交互类的渲染会更优先得到响应,而像从服务器读取数据这种IO操作就会被安排一个较低的优先级。
具体差异可以参见这个triangle动画的例子:Fiber vs Stack Demo
Fiber tree
有了fiber结构,我们可以把之前基于callstack的数据结构切换到链表,这样就有了暂停的先决条件, 那怎么判断何时暂停呢? 借助requestIdleCallback, 它提供了一种在浏览器空闲的情况下执行代码的机会
浏览器一帧的周期内所做的工作:
requestIdleCallback的执行时机:
下面来看下循环遍历fiber tree的伪代码:
performUnitWork做的工作主要如下:
reconcileChildren的作用主要是
最后生成提交渲染的过程放在commitRoot函数中,它做的工作主要是通过遍历effectlist来生成dom树,这个过程不贴代码了,感觉兴趣的同学可以自己实现下,需要注意的地方是commitRoot的过程是不可中断的。 这里主要再介绍下hooks的实现,从上面的代码可以看到fiber对象上有一个叫做hooks的数组,performUnitWork生成当前节点的elements时,会重设一个叫做hooksIdx的变量,而useState所做的工作是
其他用于保存数据的hooks的实现原理,应该也基本类似。。
想介绍的内容大概就是这些,肯定有写的不准确的地方,希望大家帮忙指正,我这边会进行修改的, 一边写文档一边犯懒癌😂,还是得多写吧,anyway, 希望对大家理解react工作原理有所帮助, 2020新年快乐🎉🎉🎉