Fiber架构

1,043 阅读10分钟

一 Fiber之前的React

下面代码实现了一个简单的react手写

let element = (
  <div id = "A1">
    <div id = "B1">
      <div id="C1"></div>
      <div id="C2"></div>
    </div>
    <div id="B2"></div>
  </div>
)

console.log(JSON.stringify(element, null, 2))
// 如果节点多,层级特别深
// 因为js是单线程,而且ui渲染和js执行是互斥的

function render(element, parentDom) {
  // 创建DOM元素
  let dom = document.createElement(element.type);
  // 处理属性
  Object.keys(element.props)
  .filter(key => key !== 'children')
  .forEach(key => {
    dom[key] = element.props[key];
  });
  if(Array.isArray(element.props.children)) {
    // 把每个子虚拟DOM变成真实DOM插入到DOM节点里
    element.props.children.forEach(child => render(child, dom));
  }
  parentDom.appendChild(dom);
}

render(
  element,
  document.getElementById('root')
);

打印出的element如下:

{
  "type": "div",
  "key": null,
  "ref": null,
  "props": {
    "id": "A1",
    "children": [
      {
        "type": "div",
        "key": null,
        "ref": null,
        "props": {
          "id": "B1",
          "children": [
            {
              "type": "div",
              "key": null,
              "ref": null,
              "props": {
                "id": "C1"
              },
              "_owner": null,
              "_store": {}
            },
            {
              "type": "div",
              "key": null,
              "ref": null,
              "props": {
                "id": "C2"
              },
              "_owner": null,
              "_store": {}
            }
          ]
        },
        "_owner": null,
        "_store": {}
      },
      {
        "type": "div",
        "key": null,
        "ref": null,
        "props": {
          "id": "B2"
        },
        "_owner": null,
        "_store": {}
      }
    ]
  },
  "_owner": null,
  "_store": {}
}

效果如下:

jsx标签化是嵌套的结构,如代码所示,最终会编译成递归执行的代码,要想中断递归是很困难的。即react16之前的调度器为栈调度器,栈浅显易懂,代码量少,但不能随意break掉,continue掉,要维护一系列的栈上下文;

二 帧

  • 目前大多数设备的屏幕刷新频率为60次/秒
  • 当每秒绘制的帧数(FPS)达到60时,页面是流畅的,小于这个值时,用户会感觉到卡顿;
  • 每个帧的预算时间是16.66毫秒(1秒/60)
  • 每个帧的开头包括样式计算,布局和绘制
  • JavaScript执行JavaScript引擎和页面渲染引擎在同一个渲染线程,GUI渲染和JavaScript执行两者是互斥的
  • 如果某个任务执行时间过长,浏览器就会推迟渲染;

三 什么是Fiber

我们可以通过某些调度策略合理分配CPU资源,从而提高用户的响应速度

通过Fiber架构,让自己的协调过程变成可被中断,适时地让出CPU执行权,可以让浏览器即使地响应用户的交互;

fiber:就是一个数据结构,它有很多属性,虚拟dom是对真实dom的一种简化;一些真实的dom都做不到的事情,那虚拟dom更做不到,于是就有了fiber,有很多属性,希望借由fiber上的这堆属性来做一些比较厉害的事情;

fiber架构

为了弥补一些不足,就设计了一些新的算法,而为了能让这些算法跑起来,所以出现了fiber这种数据结构; fiber数据结构+算法 = fiber架构;

react应用从始至终管理着基本的三样东西:

  1. Root(整个应用的根儿,一个对象,不是fiber,有个属性指向current树,同时也有个属性指向workInProgress树)

  2. current树(树上的每一个节点都是fiber,保存的是上一次的状态 并且每个fiber节点,都对应着一个jsx节点)

  3. workInProgress树(树上的每一个节点都是fiber,保存的是本次新的状态,并且每个fiber节点都对应一个jsx节点)

初次渲染的时候,没有current树 react在一开始创建Root,就会同时创建一个unintialFiber的东西(未初始化的fiber) 让react的current指向了uninitialFiber 之后再去创建一个本次要用到的workInProgress

react 中主要分两个阶段

render阶段(指的是创建fiber的过程)

  1. 为每个节点创建新的fiber(workInProgress)(可能是复用) 生成一颗有新状态的workInProgress树

  2. 初次渲染的时候(或新创建了某个节点的时候) 会将这个fiber创建真实的dom实例 并且对当前节点的子节点进行插入appendChildren,

  3. 如果不是初次渲染的话,就对比新旧的fiber的状态,将产生了更新的fiber节点,最终通过链表的形式,挂载到RootFiber

commit阶段****才是真正的要操作页面的阶段

  1. 执行生命周期

  2. 会从RootFiber上获取到那条链表,根据链表上的标识来操作页面;

3.1 Fiber是一个执行单元

Fiber是一个执行单元,每次执行完一个执行单元,React就会检查还剩下多少时间,如果没有时间就把控制权让出去

3.2 Fiber是一种数据结构

react目前的做法是使用链表,每个虚拟节点内部表示为一个Fiber;

代码如下所示:

class FiberNode {
  constructor(tag, key, pendingProps) {
    this.tag = tag; // 表示当前fiber的类型
    this.key = key;
    this.type = null // 'div' || 'h1' || Ding
    this.stateNode = null; // 表示当前fiber的实例
    this.child = null; // 表示当前fiber的子节点 每一个fiber有且只有一个指向它的firstChild
    this.sibling = null; // 表示当前节点的兄弟节点 每个fiber有且只有一个属性指向隔壁的兄弟节点
    this.return = null; // 表示当前fiber的父节点
    this.index = 0;
    this.memoizedState = null; // 表示当前fiber的state
    this.memoizedProps = null; // 表示当前fiber的props
    this.pendingProps = pendingProps; // 表示新进来的props
    this.effecTag = null; // 表示当前节点要进行何种更新
    this.firstEffect = null; // 表示当前节点的有更新的第一个子节点
    this.lastEffect = null; // 表示当前节点的有更新的最后一个子节点
    this.nextEffect = null; // 表示下一个要更新的子节点

    this.alternate = null; // 用来连接current和workInProgress的
    this.updateQueue = null; // 一条链表上面挂载的是当前fiber的新状态

    // 其实还有很多其他的属性
    // expirationTime: 0
  }
}

四 rAF

requestAnimationFrame回调函数会在绘制之前执行

  • requestAnimationFrame(callback)会在浏览器每次重绘前执行callback回调,每次callback执行的时机都是浏览器刷新下一帧渲染周期的起点上

  • requestAnimationFrame(callback)的回调callback回调参数timestamp是回调被调用的时间,也就是当前帧的起始时间

  • rAfTime performance.timing.navigationStart + performance.now()约等于Date.now();

下面代码实现了一个绘制进度条的功能;

<script>
    let div = document.querySelector('div');
    let button = document.querySelector('button');
    let startTime;
    function progress() {
      div.style.width = div.offsetWidth + 1 +'px';
      div.innerHTML = div.offsetWidth + '%';
      if(div.offsetWidth < 100) {
        console.log(Date.now() - startTime + 'ms');
        startTime = Date.now();
        requestAnimationFrame(progress);
      }
    }
    button.onclick = function() {
      div.style.width = 0;
      startTime = Date.now();
      // 浏览器会在每一帧渲染前执行progress
      requestAnimationFrame(progress);
    }
  </script>

五 requestIdleCallbac

  • 我们希望快速响应用户,让用户觉得够快,不能阻塞用户的交互

  • requestIdleCallback使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应

  • 正常帧任务完成后没超过16ms,说明时间有富余,此时就会执行requestIdleCallback里注册的任务

  • requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务,而requestIdleCallback的回调则不一定,属于低优先级任务;

<script type="text/javascript">
    function sleep(duration) {
      let start = Date.now();
      while(start + duration > Date.now()) {}
    }
    function progress() {
      console.log('progress');
      requestAnimationFrame(progress);
    }
    // requestAnimationFrame(progress);
    let channel = new MessageChannel();
    let activeFrameTime = 1000/60; // 16.6
    let frameDeadline; // 这一帧的截止时间
    let pendingCallback;
    let timeRemaining = () => frameDeadline - performance.now();
    channel.port2.onmessage = function() {
      let currentTime = performance.now();
      // 如果帧的截止时间已经小于当前时间,说明已经过期了
      let didTimeout = frameDeadline <= currentTime;
      if(didTimeout || timeRemaining() > 0) {
        if(pendingCallback) {
          pendingCallback({didTimeout, timeRemaining});
        }
      }
    }
    window.requestIdleCallback = (callback, options) => {
      requestAnimationFrame((rafTime) => {
        console.log('rafTime', rafTime);
        // 每一帧开始的时间加上16.6 就是一帧的截止时间了
        frameDeadline = rafTime + activeFrameTime;
        pendingCallback = callback;
        // 其实发消息只会,相当于添加一个宏任务
        channel.port1.postMessage('hello');
      });
    }
    const works = [
      () => {
        console.log('A1开始');
        sleep(20);
        console.log('A1结束');
      },
      () => {
        console.log('B1开始');
        sleep(20);
        console.log('B1结束');
      },
      () => {
        console.log('C1开始');
        sleep(20);
        console.log('C1结束');
      },
      () => {
        console.log('C2开始');
        sleep(20);
        console.log('C2结束');
      },
      () => {
        console.log('B2开始');
        sleep(20);
        console.log('B2结束');
      },
    ]
    // 告诉浏览器 你可以在空闲的时间执行任务,但是如果已经过期了 不管你有没有空 都要帮我执行
    requestIdleCallback(workLoop, {timeout: 1000});
    // 循环执行工具
    function workLoop(deadline) {
      console.log('本帧的剩余时间', parseInt(deadline.timeRemaining()));
      // 如果说还有剩余时间 并且还有没有完成的任务
      while((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0){
        performUnitWork();
      }
      if(works.length > 0) {
        console.log(`只剩下${deadline.timeRemaining()}, 时间片已经到期,等待下次调试`);
        requestIdleCallback(workLoop);
      }
    }
    function performUnitWork() {
      let work = works.shift();
      work();
    }

六 MessageChannel

  1. 目前requestIdleCallback只要Chrome支持

  2. 所以目前React利用MessageChannel模拟了requestIdleCallback,将回调延迟到绘制操作只后执行

  3. MessageChannel API允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据;

  4. MessageChannel创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过postMessage发送数据,而一个端口只要绑定了,就能收到另一个端口传过来的数据

  5. MessageChannel是一个宏任务;

七 Fiber执行阶段

每次渲染有两个阶段:Reconciliation(协调render阶段)和Commit(提交阶段)

  • 协调阶段:可以认为是diff阶段,这个阶段可以被中断,这个阶段会找出所有节点变更,例如节点新增,删除,属性变更等等,这些变更React称之为副作用;
  • 提交阶段:将上一个阶段计算出来的需要处理的副作用(Effetcs)一次性执行了。这个阶段必须同步执行,不能被打断;

7.1 render阶段

  1. 从顶点开始遍历
  2. 如果有第一个儿子,先遍历第一个儿子
  3. 如果没有第一个儿子,标志着此节点遍历完成
  4. 如果有弟弟遍历弟弟
  5. 如果没有下一个弟弟,返回父节点标志完成父节点遍历,如果有叔叔遍历叔叔
  6. 没有父节点遍历结束

先儿子,后弟弟,再叔叔,辈分越小越优先

什么时候一个节点遍历完成,没有子节点,或者所有子节点都遍历完成了,没爹了就表示全部遍历完成了;

7.2 commit阶段

下面代码实现了一个简易的Fiber架构,只有初次render过程;

element.js代码

export default {
  "type": "div",
  "props": {
    "id": "A1",
    "children": [
      {
        "type": "div",
        "props": {
          "id": "B1",
          "children": [
            {
              "type": "div",
              "props": {
                "id": "C1"
              }
            },
            {
              "type": "div",
              "props": {
                "id": "C2"
              }
            }
          ]
        },
      },
      {
        "type": "div",
        "props": {
          "id": "B2"
        }
      }
    ]
  }
}

index.js代码

import element from './element.js';
let container = document.getElementById('root');
const PLACEMENT = 'PLACEMENT';

// 下一个工作单元
// fiber其实也是一个普通的JS对象
let workInProgressRoot = {
  stateNode: container, // 此fiber对应的dom节点
  props: {children: [element]} // fiber的属性
}

let nextUnitOfWork = workInProgressRoot;

function workLoop(deadline) {
  // 如果有当前的工作单元,就执行它,并返回一个工作单元
  while(nextUnitOfWork && deadline.timeRemaining() > 0) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
  if(!nextUnitOfWork) {
    commitRoot();
  }
}

function commitRoot() {
  let currentFiber = workInProgressRoot.firstEffect;
  while(currentFiber) {
    console.log('commitRoot', currentFiber.props.id);
    if(currentFiber.effectTag === 'PLACEMENT') {
      currentFiber.return.stateNode.appendChild(currentFiber.stateNode);
    }
    currentFiber = currentFiber.nextEffect;
  }
  workInProgressRoot = null;
}

function performUnitOfWork(workingProgressFiber) {
  // 1 创建真实的dom, 并没有挂载 2,创建fiber子树
  beginWork(workingProgressFiber);
  if(workingProgressFiber.child) {
    return workingProgressFiber.child; // 如果有儿子,返回儿子
  }
  while(workingProgressFiber) {
    // 如果没有儿子当前节点其实就结束完成了
    completeUnitOfWork(workingProgressFiber);
    if(workingProgressFiber.sibling) { // 如果有弟弟,返回弟弟
      return workingProgressFiber.sibling;
    }
    workingProgressFiber = workingProgressFiber.return; // 先指向父亲
  }
}

function beginWork(workingProgressFiber) {
  console.log('beginWork', workingProgressFiber.props.id);
  if(!workingProgressFiber.stateNode) {
    workingProgressFiber.stateNode = document.createElement(workingProgressFiber.type);
    for (let key in workingProgressFiber.props) {
      if(key !== 'children') {
        workingProgressFiber.stateNode[key] = workingProgressFiber.props[key];
      }
    }
  }
  // 在beginwork里是不会挂载的
  let previousFiber;
  if(Array.isArray(workingProgressFiber.props.children)) {
    workingProgressFiber.props.children.forEach((child, index) => {
      let childFiber = {
        type: child.type,
        props: child.props,
        return: workingProgressFiber,
        effectTag: 'PLACEMENT', // 这个fiber对应的dom节点需要被插入到页面中
      }
      if(index === 0) {
        workingProgressFiber.child = childFiber;
      } else {
        previousFiber.sibling = childFiber;
      }
      previousFiber = childFiber;
    });
  }

}

function completeUnitOfWork(workingProgressFiber) {
  console.log('completeUnitOfWork', workingProgressFiber.props.id);
  // 构建副作用链effectList 只有那些有副作用的节点
  let returnFiber = workingProgressFiber.return;
  if(returnFiber) {
    // 把当前fiber的有副作用子链表挂到父亲身上
    if(!returnFiber.firstEffect) {
      returnFiber.firstEffect = workingProgressFiber.firstEffect;
    }
    if(workingProgressFiber.lastEffect) {
      if(returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = workingProgressFiber.firstEffect;
      }
      returnFiber.lastEffect = workingProgressFiber.lastEffect;
    }
    // 再把自己挂到后面
    if(workingProgressFiber.effectTag) {
      if(returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = workingProgressFiber;
      } else {
        returnFiber.firstEffect = workingProgressFiber;
      }
      returnFiber.lastEffect = workingProgressFiber;
    }
  }
}

// 告诉浏览器在空闲的时间执行workLoop
requestIdleCallback(workLoop);

上面代码可以实现第一章的dom结构;

八 fiber小demo

下面demo实现了初次渲染功能的fiber架构,代码已验证可以跑通;目录结构如下图所示

webpack配置

let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.[hash:8].js',
    path: path.resolve(__dirname, 'build'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.html$/,
        use: 'html-withimg-loader',
      },
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: { // 用babel-loader需要把es6-es5
            presets: [
              '@babel/preset-env',
              '@babel/preset-react'
            ],
            plugins: [
              // '@babel/plugin-proposal-class-properties'
              ["@babel/plugin-proposal-decorators", { "legacy": true }],
              ["@babel/plugin-proposal-class-properties", { "loose" : true }],
              "@babel/plugin-transform-runtime",
            ]
          }
        },
        include: path.resolve(__dirname, 'src'),
        exclude: /node_modules/,
      }
    ]
  }
}

src/index.js

import React from './react/react.js';
import ReactDom from './react-dom/react-dom.js';

class Stoney extends React.Component {
  state = {
    Stoney: 999
  }
  handleClick = () => {
    console.log('click')
  }
  render() {
    return (
      <div onClick = {this.handleClick}>
        <h1 style={{color: 'red'}}>abc</h1>
        <h2>
          123
          <p>456</p>
        </h2>
        <h3>
          <span></span>
        </h3>
      </div>
    )
  }
}

ReactDom.render(
  <Stoney key="789"></Stoney>,
  document.querySelector('#app')
)

react/react.js

const REACT_ELEMENT_TYPE = Symbol.for('react.element');

function ReactElement(type, key, props) {
  let element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type,
    key,
    props
  }
  return element;
}

function createElement(type, props = {}, children) {
  let _props = Object.assign({}, props);
  let _key = _props.key || null;
  let children_length = children.length;
  _props.children = children_length === 0 ? null : children_length === 1 ? children[0] : children;
  return ReactElement(type, _key, _props);
}

class Component {
  constructor(props, context, updater) {
    this.props = props;
    this.context = context;
    this.updater = updater || null;
  }

  get isReactComponent() {
    return true;
  }
  setState(partialState, callback) {
    if(partialState instanceof Object || typeof partialState === 'function') {
      let _setState = this.updater.enqueueSetState;
      _setState && _setState(this, partialState, callback, 'setState')
    }
  }
}

const React = {
  createElement: function(type, props, ...children) {
    let element = createElement(type, props, children);
    return element;
  },
  Component
}

export default React;

react-dom/react-dom.js

import React from '../react/react.js';

let isFirstRender = false;
let HostRoot = 'HostRoot'; // 标识RootFiber类型
let ClassComponent = 'ClassComponent'; // 表示类组件的类型
let HostComponent = 'HostComponent'; // 表示原生dom类型
let HostText = 'HostText'; // 表示文本类型
// let FunctionComponent = 'FunctionComponent'; // 表示函数组件类型

let NoWork = 'NoWork'; // 表示没有任何工作
let Placement = 'Placement'; // 表示这个节点是新插入的
let Update = 'Update'; // 表示当前节点有更新
let Deletion = 'Deletion'; // 表示当前节点要被删除
let PlacementAndUpdate = 'PlacementAndUpdate' // 一般是节点换位置同时更新了

let nextUnitOfWork = null;

let ifError = (function() {
  // 这个函数没用 就是while循环万一卡死了可以退出
  let _name = '';
  let _time = 0;
  return function(name, time) {
    _name = _name !== name ? name : _name;
    _time++;
    if(_time >= time) {
      throw `${name}函数的执行超过了${time}次`
    }
  }
})()

let eventsName = {
  onClick: 'click',
  onChange: 'change',
  onInput: 'input',
}

class FiberNode {
  constructor(tag, key, pendingProps) {
    this.tag = tag; // 表示当前fiber的类型
    this.key = key;
    this.type = null // 'div' || 'h1' || Ding
    this.stateNode = null; // 表示当前fiber的实例
    this.child = null; // 表示当前fiber的子节点 每一个fiber有且只有一个指向它的firstChild
    this.sibling = null; // 表示当前节点的兄弟节点 每个fiber有且只有一个属性指向隔壁的兄弟节点
    this.return = null; // 表示当前fiber的父节点
    this.index = 0;
    this.memoizedState = null; // 表示当前fiber的state
    this.memoizedProps = null; // 表示当前fiber的props
    this.pendingProps = pendingProps; // 表示新进来的props
    this.effectTag = NoWork; // 表示当前节点要进行何种更新
    this.firstEffect = null; // 表示当前节点的有更新的第一个子节点
    this.lastEffect = null; // 表示当前节点的有更新的最后一个子节点
    this.nextEffect = null; // 表示下一个要更新的子节点

    this.alternate = null; // 用来连接current和workInProgress的
    this.updateQueue = null; // 一条链表上面挂载的是当前fiber的新状态

    // 其实还有很多其他的属性
    // expirationTime: 0
  }
}


function createFiber(tag, key, pendingProps) {
  return new FiberNode(tag, key, pendingProps);
}

function createWorkInProgress(current, pendingProps) {
  // 复用current.alternate
  let workInProgress = current.alternate;
  if (!workInProgress) {
    workInProgress = createFiber(current.tag, current.key, pendingProps);
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    // 要让这俩东西互相指向
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;
    workInProgress.effectTag = NoWork;
    workInProgress.firstEffect = null;
    workInProgress.lastEffect = null;
    workInProgress.nextEffect = null;
  }

  // 要保证current和current.alternate上的updateQueue是同步的;
  // 因为每次执行setState的时候会创建新的更新 把更新挂载到组件对应的fiber上;
  // 这个fiber在奇数次更新时,存在于current树上 在偶数次更新时存在于current.alternate
  // 咱们每次创建(或复用)workInProgress 是从current.alternate上拿的对象
  // 复用的这个alternate上 updateQueue上不一定有新的更新
  // 所以这里要判断如果current.alternate上没有新的更新的话 就说明本轮更新找到的这个fiber 存在于current树上

  // 源码中没有这个判断
  // 在执行createWorkInProgress之前,调用了一个叫做enqueueUpdate的方法
  // 这个方法中 它将fiber和current,alternate上的updateQueue的新状态 进行了同步

  // 只有初次渲染的时候 会给组件的实例一个属性 指向它的fiber
  // 以后这个fiber 就不会再改变了
  if (!!workInProgress && !!workInProgress.updateQueue && !workInProgress.updateQueue.lastUpdate) {
    workInProgress.updateQueue = current.updateQueue;
  }

  workInProgress.child = current.child;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  return workInProgress;
}

function reconcileSingleElement(returnFiber, element) {
  let type = element.type;
  let flag = null;
  if(element.$$typeof === Symbol.for('react.element')) {
    if (typeof type === 'function') {
      if (type.prototype && type.prototype.isReactComponent) {
        flag = ClassComponent;
      }
    } else if (typeof type === 'string') {
      flag = HostComponent;
    }
    let fiber = createFiber(flag, element.key, element.props);
    fiber.type = type;
    fiber.return = returnFiber;
    return fiber;
  }
}

function reconcileSingTextNode(returnFiber, text) {
  let fiber = createFiber(HostText, null, text);
  fiber.return = returnFiber;
  return fiber;
}

function reconcileChildrenArray(workInProgress, nextChildren) {
  // 这个方法中 要通过index和key值去尽可能多的找到可以复用的dom节点
  // 这个函数就是react中最复杂的diff算法
  let nowWorkInProgress = null;
  if (isFirstRender) {
    nextChildren.forEach((reactElement, index) => {
      if(index === 0) {
        if(typeof reactElement === 'string' || typeof reactElement === 'number') {
          workInProgress.child = reconcileSingTextNode(workInProgress, reactElement)
        } else {
          workInProgress.child = reconcileSingleElement(workInProgress, reactElement)
        }
        nowWorkInProgress = workInProgress.child;
      } else {
        if(typeof reactElement === 'string' || typeof reactElement === 'number') {
          nowWorkInProgress.sibling = reconcileSingTextNode(workInProgress, reactElement);
        } else {
          nowWorkInProgress.sibling = reconcileSingleElement(workInProgress, reactElement)
        }
        nowWorkInProgress = nowWorkInProgress.sibling;
      }
    })
    // debugger;
    return workInProgress.child;
  }
}

function reconcileChildFiber(workInProgress, nextChildren) {
  // debugger;
  if (typeof nextChildren === 'object' && !!nextChildren && !!nextChildren.$$typeof) {
    // 说明它是一个独生子 并且是react元素
    return reconcileSingleElement(workInProgress, nextChildren)
  }
  if (nextChildren instanceof Array) {
    return reconcileChildrenArray(workInProgress, nextChildren)
  }
  if(typeof nextChildren === 'string' || typeof nextChildren === 'number') {
    return reconcileSingTextNode(workInProgress, nextChildren)
  }
  return null;
}

function reconcileChildren(workInProgress, nextChildren) {
  if (isFirstRender && !!workInProgress.alternate) {
    workInProgress.child = reconcileChildFiber(workInProgress, nextChildren);
    workInProgress.child.effectTag = Placement;
  } else {
    workInProgress.child = reconcileChildFiber(workInProgress, nextChildren);
  }
  return workInProgress.child;
}

function updateHostRoot(workInProgress) {
  // debugger;
  let children = workInProgress.memoizedState.element;
  return reconcileChildren(workInProgress, children);
}

function updateClassComponennt(workInProgress) {
  // debugger;
  let component = workInProgress.type;
  let nextProps = workInProgress.pendingProps;
  if(!!component.defaultProps) {
    nextProps = Object.assign({}, component.defaultProps, nextProps)
  }

  let shouldUpdate = null;
  let instance = workInProgress.stateNode;
  if(!instance) {
    // 没有实例 说明是初次渲染 或者是一个新创建的节点
    instance = new component(nextProps);
    workInProgress.memoizedState = instance.state;
    workInProgress.stateNode = instance;
    instance._reactInternalFiber = workInProgress;
    instance.updater = classComponentUpdater;

    // 用来代替componentWillReceiveProps
    let getDerivedStateFromProps = component.getDerivedStateFromProps;
    if (!!getDerivedStateFromProps) {
      let prevState = workInProgress.memoizedState
      let newState = getDerivedStateFromProps(nextProps, prevState);
      if(!(newState === null || newState === undefined)) {
        if(typeof newState === 'object' && !(newState instanceof Array)) {
          workInProgress.memoizedState = Object.assign({}, nextProps, newState)
        }
      }
      instance.state = workInProgress.memoizedState;
    }
    // 要处理一些生命周期之类的
    shouldUpdate = true;
  } else {
    // 说明不是初次渲染
  }
  // debugger;
  let nextChild = instance.render();
  return reconcileChildren(workInProgress, nextChild);
}

function updateHostComponent(workInProgress) {
  // debugger;
  let nextProps = workInProgress.pendingProps;
  let nextChildren = nextProps.children;

  // 对于文本类型的节点
  // 不一定每次都创建对应的fiber
  // 当这个节点有兄弟节点的时候会创建对应的fiber
  // 当它是独生子的时候不会创建fiber 直接返回null
  if (typeof nextChildren === 'string' || typeof nextChildren === 'number') {
    nextChildren = null;
  }
  return reconcileChildren(workInProgress, nextChildren)
}

function beginWork(workInProgress) {
  // debugger;
  let tag = workInProgress.tag;
  let next = null;

  if (tag === HostRoot) {
    next = updateHostRoot(workInProgress);
  } else if (tag === ClassComponent) {
    next = updateClassComponennt(workInProgress);
  } else if(tag === HostComponent) {
    next = updateHostComponent(workInProgress);
  } else if (tag === HostText) {
    next = null;
  }

  return next;

}

function completeWork(workInProgress) {
  // 1, 创建真实的dom实例
  let tag = workInProgress.tag;
  let instance = workInProgress.stateNode;
  if(tag === HostComponent) {
    if(!instance) {
      // 说明这个节点是初次挂载
      // 也可能是一个新创建的节点
      let domElement = document.createElement(workInProgress.type);
      domElement.__reactInternalInstance = workInProgress;
      workInProgress.stateNode = domElement;

      // 2,对子节点进行插入
      let node = workInProgress.child;
      wapper: while(!!node) {
        let tag = node.tag;
        if(tag === HostComponent || tag === HostText) {
          domElement.appendChild(node.stateNode)
        } else {
          node.child.return = node;
          node = node.child;
          continue;
        }
        if(node === workInProgress) break;

        while(node.sibling === null) {
          if(node.return === null || node.return === workInProgress) {
            break wapper
          }
          node = node.return;
        }

        node.sibling.return = node.return;
        node = node.sibling;
      }

      // 3,把属性给它
      let props = workInProgress.pendingProps;
      for (let propKey in props) {
        let propValue = props[propKey];
        if(propKey === 'children') {
          if(typeof propValue === 'string' || typeof propValue === 'number') {
            domElement.textContent = propValue;
          }
        } else if(propKey === 'style') {
          for (let stylePropKey in propValue) {
            if(!propValue.hasOwnProperty(stylePropKey)) continue;
            let styleValue = propValue[stylePropKey].trim();
            if(styleValue === 'float') {
              stylePropKey = 'cssFloat';
            }
            domElement.style[stylePropKey] = styleValue
          }
        } else if(eventsName.hasOwnProperty(propKey)) {
          // react中所有写在jsx模板上的事件 都是合成事件
          // 合成事件不会立即执行传进来的数据
          // 而是先执行一些其他东西
          // 比如事件源对象做一些处理进行合并
          // 会把你所有的事件都代理到根节点
          // 最事件代理的好处就是全局你可能只用绑定一个事件就够了
          // 再比如它内部会自己写个什么阻止冒泡的方法或阻止默认的方法
          domElement.addEventListener(eventsName[propKey], event, false);
        } else {
          domElement.setAttribute(propKey, propValue);
        }
      }
    }
  } else if (tag === HostText) {
    let oldText = workInProgress.memoizedprops;
    let newText = workInProgress.pendingProps;
    if(!instance) {
      instance = document.createTextNode(newText);
      workInProgress.stateNode = instance;
    } else {

    }
  }
}

function completeUnitOfWork(workInProgress) {
  while(true) {
    let returnFiber = workInProgress.return;
    let siblingFiber = workInProgress.sibling;

    completeWork(workInProgress);

    let effectTag = workInProgress.effectTag;
    let hasChange = (
      effectTag === Update ||
      effectTag === Deletion ||
      effectTag === Placement ||
      effectTag === PlacementAndUpdate
    )
    if (hasChange) {
      if(!!returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = workInProgress;
      } else {
        returnFiber.firstEffect = workInProgress;
      }
      returnFiber.lastEffect = workInProgress;
    }

    if(!!siblingFiber) return siblingFiber;
    if(!!returnFiber) {
      workInProgress = returnFiber;
      continue
    }
    return null;
  }

}

function performUnitOfWork(workInProgress) {
  // beginWork的目的就是根据传进去的这个workInProgress
  // 生成它的子节点的fiber
  let next = beginWork(workInProgress);

  if(next === null) {
    next = completeUnitOfWork(workInProgress);
  }

  return next;
}

function workLoop(nextUnitOfWork) {
  while(!!nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
}

let classComponentUpdater = {
  enqueueSetState() {

  }
}

function commitRoot(root, finishedWork) {
  let isWorking = true;
  let isCommitting = true;

  // 有三个循环
  // 第一个循环 用来执行getSnapshotBeforeUpdate
  // 第二个循环 真正用来操作页面 将有更新的节点 该插入的插入 该更新的更新 该删除的删除
  // 第三个循环 执行剩下的生命周期 componentDidUodate 或者 componnentDidMount
  let firstEffect = finishedWork.firstEffect;
  let nextEffect = null;

  // nextEffect = firstEffect;
  // while() {
  //
  // }

  nextEffect = firstEffect;
  while(!!nextEffect) {
    ifError('第二个循环', 50);
    let effectTag = nextEffect.effectTag;
    if(effectTag.includes(Placement)) {
      // 说明是新插入的节点
      // 1,先找到一个能被插进来的父节点
      // 2,在找能往父节点中插入的子节点
      let parentFiber = nextEffect.return;
      let parent = null;
      while(!!parentFiber) {
        let tag = parentFiber.tag;
        if(tag === HostComponent || tag === HostRoot) {
          break
        }
      }
      if(parentFiber.tag === HostComponent) {
        parent = parentFiber.stateNode;
      } else if(parentFiber.tag === HostRoot) {
        parent = parentFiber.stateNode.container;
      }

      if(isFirstRender) {
        let tag = nextEffect.tag;
        if(tag === HostComponent || tag === HostText) {
          parent.appendChild(nextEffect.stateNode);
        } else {
          let child = nextEffect;
          while(true) {
            ifError('第二个循环中的循环', 50);
            let tag = child.tag;
            if(tag === HostComponent || tag === HostText) {
              break;
            }
            child = child.child;
          }
          parent.appendChild(child.stateNode);
        }
      }
    } else if(effectTag === Update) {
      // 说明可能有更新
    } else if(effectTag === Deletion) {
      // 说明该节点要被删除
    } else if(effectTag === PlacementAndUpdate) {
      // 说明该节点可能是换了位置并且属性上有更新
    }
  }

  // nextEffect = firstEffect;
  // while() {
  //
  // }

  isWorking = false;
  isCommitting = false;
}

function completeRoot(root, finishedWork) {
  root.finishedWork = null;
  commitRoot(root, finishedWork);
}

class ReactRoot {
  constructor(container) {
    this._internalRoot = this._createRoot(container)
  }
  _createRoot(container) {
    let uninitialFiber = this._createUninitialFiber(HostRoot, null, null);
    let root = {
      container: container,
      current: uninitialFiber,
      finishedWork: null
    }

    uninitialFiber.stateNode = root;
    return root
  }
  _createUninitialFiber(tag, key, pendingProps) {
    return createFiber(tag, key, pendingProps)
  }

  render(reactElement, callback) {
    console.log('render')
    let root = this._internalRoot;

    // RootFiber
    let workInProgress = createWorkInProgress(root.current, null);
    // react 源码里头是先把这个reactElement先放到了current
    workInProgress.memoizedState = { element: reactElement }

    nextUnitOfWork = workInProgress;
    workLoop(nextUnitOfWork);
    root.finishedWork = root.current.alternate;

    if(!!root.finishedWork) {
      completeRoot(root, root.finishedWork)
    }
  }
}

const ReactDOM = {
  render(reactElement, container, callback) {
    isFirstRender = true;
    let root = new ReactRoot(container)

    container._reactRootContainer = root;
    root.render(reactElement, callback)


    isFirstRender = false;
  }
}

export default ReactDOM;

九 fiber架构本质

循环条件:利用requestIdleCallback空闲时间递减

遍历过程:利用链表,找孩子找兄弟找父亲;