19. Write a mini-react

Video courses (efficient Learning) :Into the curriculum

Course Contents:

1. Introduction and questions

2. React design philosophy

React source code architecture

4. Source directory structure and debugging

5. JSX & core API

Legacy and Concurrent mode entry functions

7. Fiber architecture

8. Render phase

9. The diff algorithm

10. com MIT stage

11. Life cycle

12. Status update process

13. Hooks the source code

14. Handwritten hooks

15.scheduler&Lane

16. Concurrent mode

17.context

18 Event System

19. Write the React mini

20. Summary & Answers to interview questions in Chapter 1

21.demo

What’s the difference between mini React and real source code

  • In the Render phase we traversed the entire Fiber tree, in the source code if nothing changes the node will hit the optimized logic, and then skip the traversal of the node
  • * * * * * * * * * * * * * * * * * * * * * * *
  • Each time we iterate we are creating a new node, some conditions in the source code will reuse the node
  • Priority is not used

Step 1: Renderer and entry function

const React = {
  createElement,
  render,
};

const container = document.getElementById("root");

const updateValue = (e) = > {
  rerender(e.target.value);
};

const rerender = (value) = > {
  const element = (
    <div>
      <input onInput={updateValue} value={value} />
      <h2>Hello {value}</h2>
    </div>
  );
  React.render(element, container);
};

rerender("World");

Copy the code

Step 2: Create the DOM node function

/ create elementfunction createElement(type, props, ... children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) = > (typeof child === "object" ? child : createTextElement(child))),
    },
  };
}

// Create a text type
function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT".props: {
      nodeValue: text,
      children: [].}}; }/ / create the dom
function createDom(fiber) {
  const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);

  updateDom(dom, {}, fiber.props);

  return dom;
}
Copy the code

Step 3: Update the node function

const isEvent = (key) = > key.startsWith("on");
const isProperty = (key) = >key ! = ="children" && !isEvent(key);
const isNew = (prev, next) = > (key) = >prev[key] ! == next[key];const isGone = (prev, next) = > (key) = >! (keyin next);

// Update node properties
function updateDom(dom, prevProps, nextProps) {
  // Delete old events
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) = >! (keyin nextProps) || isNew(prevProps, nextProps)(key))
    .forEach((name) = > {
      const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  // Delete the old attributes
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) = > {
      dom[name] = "";
    });

  // Set new properties
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) = > {
      dom[name] = nextProps[name];
    });

  // Add new events
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) = > {
      const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}
Copy the code

Step 4: Render stage

/ / render phase
function performUnitOfWork(fiber) {
  if(! fiber.dom) { fiber.dom = createDom(fiber); }const elements = fiber.props.children;
  reconcileChildren(fiber, elements);

  if (fiber.child) {
    return fiber.child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      returnnextFiber.sibling; } nextFiber = nextFiber.parent; }}// Mediate the node
function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;
  while(index < elements.length || (oldFiber ! = =null&& oldFiber ! = =undefined)) {
    const element = elements[index];
    let newFiber = null;

    const sameType = oldFiber && element && element.type === oldFiber.type;

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE"}; }if(element && ! sameType) { newFiber = {type: element.type,
        props: element.props,
        dom: null.parent: wipFiber,
        alternate: null.effectTag: "PLACEMENT"}; }if(oldFiber && ! sameType) { oldFiber.effectTag ="DELETION";
      deletions.push(oldFiber);
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }

    if (index === 0) {
      wipFiber.child = newFiber;
    } else if(element) { prevSibling.sibling = newFiber; } prevSibling = newFiber; index++; }}Copy the code

Step 5: Commit phase

/ / the commit phase
function commitRoot() {
  deletions.forEach(commitWork);
  commitWork(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
}

// Manipulate the real DOM
function commitWork(fiber) {
  if(! fiber) {return;
  }

  const domParent = fiber.parent.dom;
  if (fiber.effectTag === "PLACEMENT"&& fiber.dom ! = =null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE"&& fiber.dom ! = =null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom);
  }

  commitWork(fiber.child);
  commitWork(fiber.sibling);
}
Copy the code

Step 6: Start scheduling

// Start rendering entry
function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

// Scheduling function
function workLoop(deadline) {
  let shouldYield = false;
  while(nextUnitOfWork && ! shouldYield) {/ / render phase
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }

  if(! nextUnitOfWork && wipRoot) { commitRoot();/ / the commit phase
  }

  requestIdleCallback(workLoop); // Idle scheduling
}

requestIdleCallback(workLoop);
Copy the code

The complete code

/ / create the element
function createElement(type, props, ... children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) = > (typeof child === "object" ? child : createTextElement(child))),
    },
  };
}

// Create a text type
function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT".props: {
      nodeValue: text,
      children: [].}}; }/ / create the dom
function createDom(fiber) {
  const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);

  updateDom(dom, {}, fiber.props);

  return dom;
}

const isEvent = (key) = > key.startsWith("on");
const isProperty = (key) = >key ! = ="children" && !isEvent(key);
const isNew = (prev, next) = > (key) = >prev[key] ! == next[key];const isGone = (prev, next) = > (key) = >! (keyin next);

// Update node properties
function updateDom(dom, prevProps, nextProps) {
  // Delete old events
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) = >! (keyin nextProps) || isNew(prevProps, nextProps)(key))
    .forEach((name) = > {
      const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  // Delete the old attributes
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) = > {
      dom[name] = "";
    });

  // Set new properties
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) = > {
      dom[name] = nextProps[name];
    });

  // Add new events
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) = > {
      const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}

/ / the commit phase
function commitRoot() {
  deletions.forEach(commitWork);
  commitWork(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
}

// Manipulate the real DOM
function commitWork(fiber) {
  if(! fiber) {return;
  }

  const domParent = fiber.parent.dom;
  if (fiber.effectTag === "PLACEMENT"&& fiber.dom ! = =null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE"&& fiber.dom ! = =null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom);
  }

  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

let nextUnitOfWork = null;
let currentRoot = null;
let wipRoot = null;
let deletions = null;

// Start rendering entry
function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

// Scheduling function
function workLoop(deadline) {
  let shouldYield = false;
  while(nextUnitOfWork && ! shouldYield) {/ / render phase
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }

  if(! nextUnitOfWork && wipRoot) { commitRoot();/ / the commit phase
  }

  requestIdleCallback(workLoop); // Idle scheduling
}

requestIdleCallback(workLoop);

/ / render phase
function performUnitOfWork(fiber) {
  if(! fiber.dom) { fiber.dom = createDom(fiber); }const elements = fiber.props.children;
  reconcileChildren(fiber, elements);

  if (fiber.child) {
    return fiber.child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      returnnextFiber.sibling; } nextFiber = nextFiber.parent; }}// Mediate the node
function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;
  while(index < elements.length || (oldFiber ! = =null&& oldFiber ! = =undefined)) {
    const element = elements[index];
    let newFiber = null;

    const sameType = oldFiber && element && element.type === oldFiber.type;

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE"}; }if(element && ! sameType) { newFiber = {type: element.type,
        props: element.props,
        dom: null.parent: wipFiber,
        alternate: null.effectTag: "PLACEMENT"}; }if(oldFiber && ! sameType) { oldFiber.effectTag ="DELETION";
      deletions.push(oldFiber);
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }

    if (index === 0) {
      wipFiber.child = newFiber;
    } else if(element) { prevSibling.sibling = newFiber; } prevSibling = newFiber; index++; }}const React = {
  createElement,
  render,
};

const container = document.getElementById("root");

const updateValue = (e) = > {
  rerender(e.target.value);
};

const rerender = (value) = > {
  const element = (
    <div>
      <input onInput={updateValue} value={value} />
      <h2>Hello {value}</h2>
    </div>
  );
  React.render(element, container);
};

rerender("World");

Copy the code