1 Fiber before React

The following code implements a simple react script

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)) Function render(element, parentDom) {// Create a DOM element let DOM = document.createElement(element.type); // Handle the attribute object.keys (element.props).filter(key => key! == 'children') .forEach(key => { dom[key] = element.props[key]; }); If (Array. IsArray (element. Props. Children)) {/ / each virtual DOM into real DOM is inserted into the DOM node element. The props. Children. The forEach (child = > render(child, dom)); } parentDom.appendChild(dom); } render( element, document.getElementById('root') );Copy the code

The printed element looks like this:

{
  "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": {}
}
Copy the code

The effect is as follows:

JSX tagging is a nested structure, as shown in the code, that eventually compiles into code that executes recursively, which is difficult to break. The scheduler before React16 is the stack scheduler. The stack is simple and easy to understand, and the code is small. However, it cannot break or continue the stack at will, and a series of stack contexts must be maintained.

The second frame

  • The current screen refresh rate on most devices is 60 times per second
  • When the number of frames drawn per second (FPS) reaches 60, the page is smooth, and below that, the user feels sluggish.
  • The budget time for each frame is 16.66 ms (1 second /60)
  • The beginning of each frame includes style calculation, layout and drawing
  • JavaScript execution JavaScript engine and page rendering engine are on the same rendering thread, GUI rendering and JavaScript execution are mutually exclusive
  • If a task takes too long, the browser will delay rendering;

Three What is Fiber

Some scheduling policies can be used to properly allocate CPU resources to improve user response times

With Fiber, the coordination process can be interrupted, and the CPU execution can be removed at the right time, allowing the browser to be responsive to user interactions.

Fiber: A data structure with many properties. The virtual DOM is a simplification of the real DOM. Some of the things that the real DOM can’t do, the virtual DOM can’t do, so fiber has a lot of properties, and I want to do some cool things with all of these properties on Fiber;

The fiber architecture

In order to make up for some shortcomings, some new algorithms are designed. In order to make these algorithms run, fiber data structure appears. Fiber data structure + algorithm = Fiber architecture;

The React app manages three basic things from start to finish:

  1. Root(the Root of the entire application, an object, not Fiber, with a property pointing to the current tree and a property pointing to the workInProgress tree)

  2. Current tree (each node in the tree is fiber, which stores the last state and each fiber node corresponds to a JSX node)

  3. WorkInProgress tree (each node in the tree is fiber, saving the new state, and each fiber node corresponds to a JSX node)

When first rendered, there was no current tree react created Root at the beginning, The react current points to the uninitialFiber and then creates a workInProgress that we’ll use this time

React has two main phases

Render phase (referring to the process of creating fiber)

  1. Create a new fiber(workInProgress)(possibly multiplexed) for each node to generate a workInProgress tree with a new state

  2. First render (or when a new node is created) creates a real DOM instance of this fiber and inserts appendChildren of the current node.

  3. If not for the first time, comparing the state of the old and new Fiber will result in an updated Fiber node, which will eventually be attached to the RootFiber as a linked list

The commit phase **** is where the page is actually manipulated

  1. Execution life cycle

  2. The link list is retrieved from RootFiber and the page is manipulated according to the identifier on the link list;

3.1 Fiber is an execution unit

Fiber is an execution unit. After each execution unit, React checks how much time is left and cedes control if there is no time left

3.2 Fiber is a data structure

React currently uses linked lists where each virtual node is represented internally as a Fiber;

The code looks like this:

class FiberNode { constructor(tag, key, pendingProps) { this.tag = tag; // This. Key = key; this.type = null // 'div' || 'h1' || Ding this.stateNode = null; This. child = null; FirstChild this.sibling = null; // firstChild this.sibling = null; This. return = null; this.return = null; this.return = null; // This. Index = 0; this.memoizedState = null; MemoizedProps = null; // memoizedProps = null; Fiber props this. PendingProps = pendingProps; EffecTag = null; // if the new effecTag is powered this. EffecTag = null; This. firstEffect = null; // this. LastEffect = null; This. nextEffect = null; this.nextEffect = null; // this. Alternate = null; // This. updateQueue = null; // expirationTime: 0}}Copy the code

Four rAF

The requestAnimationFrame callback is executed before drawing

  • RequestAnimationFrame (Callback) executes the callback before each browser redraw, each time the browser refreshes at the start of the next frame’s rendering cycle

  • RequestAnimationFrame (callback) Callback callback parameter TIMESTAMP is the time at which the callback was called, i.e. the start time of the current frame

  • RAfTime performance. Timing. NavigationStart + performance. Now () is approximately equal to the Date, now ();

The following code implements a draw progress bar function;

<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(); // The browser executes progress requestAnimationFrame(progress) before each frame is rendered; } </script>Copy the code

Five requestIdleCallbac

  • We want to respond quickly to the user, fast enough for the user, without blocking the user’s interaction

  • RequestIdleCallback enables developers to perform background and low-priority work on the main event loop without affecting the delay of critical events such as animations and input responses

  • If the normal frame task does not exceed 16ms, it indicates that there is more time. At this point, the task registered in requestIdleCallback will be executed

  • RequestAnimationFrame’s callback will be executed on each frame and is a high priority task, while requestIdleCallback’s callback is not necessarily a low priority task.

<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; let didTimeout = frameDeadline <= currentTime; if(didTimeout || timeRemaining() > 0) { if(pendingCallback) { pendingCallback({didTimeout, timeRemaining}); } } } window.requestIdleCallback = (callback, options) => { requestAnimationFrame((rafTime) => { console.log('rafTime', rafTime); FrameDeadline = rafTime + activeFrameTime; pendingCallback = callback; / / message will only actually, equivalent to add a macro task channel. The port1. PostMessage (" hello "); }); } const works = [() = > {the console. The log (' A1 start); sleep (20); the console. The log (' end of the A1);}, () = > {the console. The log (' B1 begins); sleep (20); the console. The log (' end of B1);}, () = > {the console. The log (' C1 start); sleep (20); the console. The log (' end of C1);}, () = > {the console. The log (' C2 start); sleep (20); the console. The log (' end of C2);}, () => {console.log('B2 start '); sleep(20); console.log('B2 end ');},] RequestIdleCallback (workLoop, {timeout: 1000}); // function workLoop(deadline) {console.log(' time left in this frame ', parseInt(deadline. TimeRemaining ())); / / if there is time remaining And there is no task while (deadline. TimeRemaining () > 0 | | deadline. DidTimeout) && works. The length > 0) { performUnitWork(); } if(works.length > 0) {console.log(' only ${deadline. TimeRemaining ()}, time slice is expired, waiting for next debugging '); requestIdleCallback(workLoop); } } function performUnitWork() { let work = works.shift(); work(); }Copy the code

Six MessageChannel

  1. Currently requestIdleCallback is supported only by Chrome

  2. So React currently emulates the requestIdleCallback using the MessageChannel, delaying the callback until after the draw operation

  3. The MessageChannel API allows you to create a new MessageChannel and send data through its two MessagePort properties;

  4. MessageChannel creates a communication channel with two ports, each of which can send data via postMessage, and one port, once bound, can receive data from the other port

  5. MessageChannel is a macro task;

Seven Fiber execution phase

Each render has two phases: Reconciliation and Commit

  • Coordinate phase: this phase can be considered as the diff phase, which can be interrupted. This phase detects all node changes, such as node additions, deletions, property changes, etc. React calls these changes side effects.
  • Commit phase: The side effects (Effetcs) calculated in the previous phase are executed at once. This phase must be executed synchronously and without interruption;

7.1 render phase

  1. You start with the vertices
  2. If there is a first son, iterate over the first son first
  3. If there is no first son, the node is traversed
  4. If I have a brother going over a brother
  5. If there is no next brother, return the parent flag to complete the parent traversal, if there is an uncle traversal uncle
  6. No parent node traversal ends

Son first, brother second, uncle second, the younger the more priority

When a node is traversed and there are no children, or when all children are traversed and there are no parents, the traversal is complete.

7.2 the commit phase

The following code implements a simple Fiber architecture with only the first render process;

Element. The js code

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"
        }
      }
    ]
  }
}
Copy the code

The index. The js code

import element from './element.js'; let container = document.getElementById('root'); const PLACEMENT = 'PLACEMENT'; Let workInProgressRoot = {stateNode: container, // The dom node corresponding to this fiber is props: {children: [element]} // fiber attribute} let nextUnitOfWork = workInProgressRoot; Function workLoop(deadline) {// If there is a current unit of work, execute it. 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 create a real dom without mounting it; if(workingProgressFiber.child) { return workingProgressFiber.child; } while(workingProgressFiber) {completeUnitOfWork(workingProgressFiber); If (workingProgressFiber. () {/ / if there is a brother, return brother return workingProgressFiber. (; } workingProgressFiber = workingProgressFiber.return; / / points to the first father}} function beginWork (workingProgressFiber) {the console. The 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]; }} // Let previousFiber is not mounted in beginwork; if(Array.isArray(workingProgressFiber.props.children)) { workingProgressFiber.props.children.forEach((child, index) => { let childFiber = { type: child.type, props: child.props, return: workingProgressFiber, effectTag: 'PLACEMENT', / / the fiber corresponding dom node needs to be inserted into the page} the if (index = = = 0) {workingProgressFiber. Child = childFiber; } else { previousFiber.sibling = childFiber; } previousFiber = childFiber; }); } } function completeUnitOfWork(workingProgressFiber) { console.log('completeUnitOfWork', workingProgressFiber.props.id); / / build side chain effectList let only those who have the side effect of node returnFiber = workingProgressFiber. Return; If (returnFiber) {// Attach the current fiber sublist to the parent if(! returnFiber.firstEffect) { returnFiber.firstEffect = workingProgressFiber.firstEffect; } if(workingProgressFiber.lastEffect) { if(returnFiber.lastEffect) { returnFiber.lastEffect.nextEffect = workingProgressFiber.firstEffect; } returnFiber.lastEffect = workingProgressFiber.lastEffect; } / / put yourself on the back an if (workingProgressFiber. EffectTag) {if (returnFiber. LastEffect) {returnFiber. LastEffect. NextEffect = workingProgressFiber; } else { returnFiber.firstEffect = workingProgressFiber; } returnFiber.lastEffect = workingProgressFiber; }} // Tell the browser to execute workLoop requestIdleCallback(workLoop) at an idle time;Copy the code

The above code implements the DOM structure of Chapter 1;

Eight fiber small demo

The following demo has realized the fiber structure of the initial rendering function, and the code has been verified to run; The directory structure is shown in the following figure

Webpack configuration

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: {// Use babel-loader to preset es6-ES5: [ '@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/, } ] } }Copy the code

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')
)
Copy the code

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;Copy the code

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;
Copy the code

Nine Fiber architecture essence

Loop condition: Use requestIdleCallback idle time decrement

Traversal process: using the linked list, find children, find brothers, find father;