深入react源码 之深入了解fiber与fiber架构(2)

1,337 阅读8分钟

fiber数据结构

  上一篇简单阐述了一下我自己理解的fiber以及fiber架构,那么这篇来说一说fiber这个数据结构上的具体属性。


createFiber.jpg:

  图片中是react的源码,在ReactFiber.js文件中可以看到,react是通过一个叫做createFiber的方法去创建一个fiber的。内部主要是通过 new FiberNode 来返回新的fiber。
  接下来我们来看一看FiberNode是怎么创建的fiber节点的。


ReactFiber.js :

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
    this.tag = tag; // 标记不同组件类型 如classComponent表示class类组件 functionComponent表示函数类型组件 还有其他类型的在 ReactWorkTag.js 文件红可以看到 
    this.key = key; // react元素上的key 就是jsx上写的那个key
    this.elementType = null; // 表示fiber的真实类型 比如当前fiber对应的jsx是div 那这个属性就是 'div' 如果这个属性对应一个叫做 Test 的class类 那么这个属性就是 Test 本身
    this.type = null; // 表示fiber的真实类型 这个和elementType大部分情况下是一样的 在使用了懒加载之类的功能时可能会不一样
    this.stateNode = null; // 当前Fiber对应的实例 比如class组件 new完之后就挂在这个属性上
    this.return = null; // 父级Fiber 用来指向当前fiber的父fiber
    this.child = null; // 子级Fiber 指向自己的第一个子Fiber节点 也就是firstChildFiber
    this.sibling = null; // 兄弟节点 指向右边的兄弟节点
    /*
        可能您已经注意到了,fiber上有个属性叫child 但是却没有一个属性叫children
        但是我们的组件中肯定会存在一个父节点下有多个子节点的情况
        那为啥fiber中没有children属性呢~
        其实react中,采用的是“树”还有“链表”这两种数据结构
        这两种数据结构被合在一起使用了
        每个节点 有且仅有一个child属性指向他的firstChild
        每个节点 有且仅有一个sibling属性指向他的右边的兄弟节点
        比如说:
            <div>
                <h1></h1>
                <MyComponent />
                <span>
                    <h2>6666</h2>
                </span>
            </div>
        想上面这个jsx结构 就会形成一颗树和链表的树
        div
         ┃
         ┃ (child)
         ▼
        h1 ————————→ MyComponent ————————→ span  
           (sibling)             (sibling)   ┃
                                             ┃ (child)
                                             ▼
                                             h2
        div.child → h1
        h1.sibling → MyComponent
        MyComponent.sibling → span
        span.child → h2  
    */
    
    
    this.index = 0; // 一般如果没有兄弟节点的话是0 当某个父节点下的子节点是数组类型的时候会给每个子节点一个index index和key要一起做diff
    this.ref = null; // 这个就是react元素上也就是jsx上写的ref
    this.pendingProps = pendingProps; // 新传进来的props
    this.memoizedProps = null; // 上次渲染完后的旧的props
    this.updateQueue = null; // 该fiber上的更新队列 执行一次setState就会忘这个属性上挂一个新的更新 这些更新以链表的形式存在
    this.memoizedState = null; // 旧的state 也表示当前页面上的你能看到的状态 不只是class组件有 function类型组件也可能有
    this.firstContextDependency = null; // 这个没啥b用 跟老版本的context有关 旧版本的context马上就要被废了

    /* 
      这里的mode要用二进制表示 ReactTypeOfMode.js 文件中可以看到
      mode目前有4中 用来表示当前组件下的所有子组件要用处于一种什么状态
      比如concurrentMode就表示当前子节点们要异步进行更新
      strictMode就表示当前子节点们要处于严格模式
     */
    this.mode = mode;
    // Effects
    this.effectTag = NoEffect; // 表示当前fiber要进行何种更新 ReactSideEffectTag.js 文件中可以看到全部更新类型 比如placement表示是新创建的节点 update表示属性可能有变化或者有生命周期之类的
    this.nextEffect = null; // 一条链表 指向下一个有更新的fiber
    this.firstEffect = null; // 子节点中所有有更新的节点中的第一个fiber
    this.lastEffect = null; // 子节点中所有有更新的节点中的最后一个fiber
    /*
        这个effect有必要好好解释一下
        effectTag表示当前这个fiber节点本身有何种更新 没有更新的话就是 NoEffect
        firstEffect是链表
        lastEffect当前fiber的所有子fiber中的最后一个有更新的fiber
        firstEffec这条链表上的fiber从first指向last
        表示的是当前节点的子节点们!注意是子节点不包括自身
        并且是所有有更新的子节点的链表
        也就是说 如果当前fiber下的某个子节点的effectTag是NoEffect的话
        那么这个子节点就不会被包括在这条链表上
        不好理解的话直接看例子!

        <div>                           <div id="haschange">
            <h1>123</h1>                    <h1>456</h1>
            <h2>text</h2>    setState       <h2>text</h2>
            <span>          ——————————→     <span class="haschange">
                <a>888</a>                     <a>888</a>
                <p></p>                     </span>    
            </span>                         <h3>333</h3>
        </div>                          </div>


        比如说上面这种情况
        最外层的div的id发生了改变 所以div的effectTag就是Update
        h1的子节点 也就是这个文本从123变成了456 所以h1的effectTag也是Update
        h2没有发生任何改变 所以h2的effectTag是NoEffect
        span的class发生了改变 所以effectTag是Update
        a标签也没改变 effectTag也是NoEffect
        但是p标签被删除了 所以p节点对应的fiber的effectTag是Deletion
        最后还新插如了一个h3标签 由于是新插入的 所以h3的effectTag是Placement

        这样的话 我们来看一看每个节点的fiber的effect链表是个什么样的状态

        首先最外层的div,它的子节点一共有5个,分别是h1 h2 span a p(注意,孙子节点也算div的子节点)
        其中有更新的是 h1 p span h3(h3 是新插入的) 一共这四个有更新
        这四个有更新的fiber要本着深度优先的规则 形成链表挂载div的fiber上
        所以div的fiber的lastEffect就是: h3
        然后div的fiber的firstEffect这条链表就是: h1 → p → span → h3(注意是从p指向span 因为是深度优先)
        但是由于div自己本身没有更新 所以div的effectTag仍然是NoEffect
        
        然后是 h1 和 h2 以及 h3 ,因为这仨的子节点都是文本节点 文本节点有可能不产生对应的fiber,
        所以这仨他们的firstEffect和lastEffect都是null。
        但是其中 h1 和 h3 自己本身有更新 所以会被挂载到他们的父节点 也就是div的effect链表上。

        但是span就不一样了
        span下面的p节点被删除了 而且span下只有这一个节点发生了改变
        所以span的fiber的 lastEffect === firstEffect === p的fiber
        (注意!只等于p的fiber,虽然span自身也有更新,但是自身的更新不挂载到自己的effect链表上)
        然后span的自身更新了class 所以它自身的effectTag属性是Update

        有了这个例子应该就可以明白,任何节点的子节点如果有更新 都会把它的子节点做成链表挂载在它身上
        而如果这个节点自身也有更新,那这个节点会被挂到它的父节点的effect链表上

    */

    this.expirationTime = NoWork; // 当前fiber的优先级 也可以说是过期时间
    this.childExpirationTime = NoWork; // 当前节点的所有子节点中的那个最大的优先级
    /*
        expirationTime 比较复杂 以后慢慢说 同步状态下几乎用不到
        也就是说如果不给组件包裹concurrent组件的话 几乎没太大用
    */

    this.alternate = null; // 指向当前fiber的上一个状态
    /*
        alternate指向当前fiber的上一个fiber
        啥意思呢,就是说
        初次渲染 当前jsx节点会有个fiber 假设名字叫 ding1
        然后setState一次会生成一个新的fiber 假设名字叫ding2
        那么ding1.alternate 就可能指向ding2 然后ding2.alternate就可能指向ding1
        他们是双向的
    */
}

  react中通过 new FiberNode 这种方式创建一个新的fiber react组件中的每一个jsx节点都对应一个fiber 不管是原生dom还是自定义的组件 函数组件 class组件或者说react自己提供的一些组件 都对应一个fiber。
  但是文本类型的节点有点特殊 怎么个特殊法呢?
    1.当这个文本节点没有其他兄弟节点的情况,也就是说它的父节点只有他一个子节点,这种情况该文本节点不产生fiber,或者说直接让这个文本类型的节点的fiber是null。这是第一种情况
    2.当这个文本节点有兄弟节点的时候,就是说它的父节点下有很多子节点,这种情况,会为该文本节点创建一个文本类型的fiber。
    以上对于文本类型的节点情况比较特殊,一定要记住。除此之外的所有节点基本都会被创建自己对应的fiber
  以上就是fiber这种数据结构的所有属性。 有些属性乍一看肯定不知道是用来干嘛的 没事儿,以后咱慢慢写~

gayhub地址:github.com/y805939188/… 跪求大佬赏星星

下一篇: fiber架构