Refer to version 16.x

Function of 2 packages

  • import React from './react';
  • import ReactDOM from './react-dom';
  • These two packages are introduced by default every time I write,ReactDOMThis one mainly provides the Render method, which mounts the React element to the page.ReactIs the core package from which most of the React API comes, for exampleCreateElement Component createRef createContext, etc

jsx

  • JSX: a syntax that mixes JS and HTML. JSX is syntactic sugar that will be escaped via BabelReact.createElementSyntax (React had to be introduced before version 16 for this reason)
  • JSX syntax is translated into JS via Babelbabel-loader @babel/core @babel/preset-react(By default, he is responsible for converting HTML tags into JS code)
<div>123</div> Babel escape ===> react. createElement("div".null."123");
Copy the code

createElement

  • React.createElement creates the component from the compiled JSX syntax, taking three arguments
    • The first is a component or tag
    • The second is the tag’s configuration object ID, style, and so on
    • The third one is and all the ones after it are children, which could be an object, could be an array
  • The static isReactComponent property is used to distinguish function components from class components in render
  • Pseudo code
function createElement(type,config,children){
  let propName;
  const props = {};
  for(propName in config){
    props[propName] = config[propName]
  }
  const childrenLength = arguments.length - 2// Let's see how many sons we have
  if(childrenLength === 1){
    props.children = children;// props. Children is an ordinary object
  }else if(childrenLength >1) {// If the number of sons is greater than 1, props. Children is an array
    props.children = Array.from(arguments).slice(2);
  }
  return {type,props}
}
class Component{
  static isReactComponent = true
  constructor(props){
    this.props = props
  }
}
export default {
  createElement,
  Component
}
Copy the code

render

  • The render function is the render functionReact.createElementThe created virtual DOM is converted into the real DOM and mounted on the second parameter. The core iscreateDOMfunction
  • createDOMIt’s sorted by the elements that come inPlain text class native DOM functionThe new DOM will be returned and then mounted to root. For most react groups, react levels elements of the same level
// Draw will end up like this
{[1], [2], [3[4]]} = = = {[1.2.3.4]}
Copy the code

Apply colours to a drawing

  • Parse the object to render:Native JS class fNCtion
  • React.createElementUsed to create the virtual DOM, he mainly classifies the elements intoNative JS class fNCtion, wrapped as an object, and recursively processed if the child node is multilayered.
import React from './react';
import ReactDOM from './react-dom';
function FunctionCounter(props){
  return React.createElement('div', {id: 'counter'}, 'hello2'.'123');
}
class ClassComponent extends React.Component{
  render() {
    return React.createElement(FunctionCounter, {id: 'counter'}, 'hello1');
  }
}
ReactDOM.render(
  elm,
  document.getElementById('root'));Copy the code

What Babel looks like when compiled

let onClick1 = (event) = > {
  console.log('onclick1',event);
}
let onClick = (event) = > {
  console.log('onclick', event);
  event.persist();
  setInterval(() = >{
    console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --')},1000)}// js variable names are basically humps
/ / writing
let elm = React.createElement('div',
  {
   id:'sayHello',
   onClick,
   style: {with:'100px'.height:'100px'.border:'1px solid red'}},'div',
  React.createElement('section', {
      onClick:onClick1,
      style: {
        color: 'red'.with:'50px'.height:'50px'.border:'1px solid green'
      }},
      'section'))Copy the code

Synthetic events

  • 1. Because synthesized events can mask browser differences, different browsers bind events and trigger events in different ways
  • 2, synthesis wide to achieve the reuse of event objects, reuse, reduce garbage collection, improve performance
  • 3, because I want to implement batch update setState by default, setState two setStates merge into one update, this is also implemented in the composite event

Event flow, all events are bubbled to document for unified management

  • For example, when a Click event is triggered, it will iterate from the target source to the document, fetching each element that needs to be processed and binding the click event function to trigger it
  • Event persist is the persistence of event events. By default, when a synthesized event is executed, the data in the synthesized event is pointed to null. Persist: execute event.persist() while the current function is executing. This will alter the internal composited event pointer and wait for the event traversal to complete to clear the composited event. The previous synthesized event was not cleared
  • Handle batch update, start batch update mode before processing events, and then handle setState in the function. After all the event functions are executed, close the batch update mode and update the page

setState

  • There is a variable to control batch update. By default, batch update is enabled.
  • In event handling, synchronous case, each callsetStateHe saves the state and tries to update the class component.
    • If you are still in a batch update situation, put yourself in the update queue. If you call it multiple times, it’s going to go all the way over here and save the data
    • If you’re not currently doing batch updates, do them, do themshouldUpdateUpdate the hook logic on the componentforceUpdateForced to update
  • Class component is wide to callforceUpdateForce updates to components.
  • import { unstable_batchedUpdates } from './react-dom'Forced to update
    • unstable_batchedUpdatesThe logic is simple, force the batch update to start, execute the logic passed in, turn off the batch update, call the queue update, and update the component

forceUpdate

  • Each time the setState execution completes the component’s attempt to update, first determineshouldUpdateWhether the hook is supported and only called if it isforceUpdatePerform forcible update. Alternatively, we can call it directly from the class componentforceUpdateForce update
  • I can walk herecomponentWillMount & getSnapshotBeforeUpdate & render & componentDidUpdateThe hook executes once, and after render, the old and new DOM is updated

diff

  • 1. If there is no new element, the type is different, and the text is different, replace it directly
  • 2. If they are both class and function components, they enter the loop body again for DOM comparison
  • 3. The core compares two native elements of the same type with the following exampleA B C DandA C B E F
    • The first is diffQueue collection, which collects the DOM that needs to be operated on
    • The second point is that patch uniformly processes DOM
// 1. Patch Prints the DOM that diffQueue needs to operate
/ / /
/ / {
// "parentNode": {
// "eventStore": {}
/ /},
// "type": "MOVE",
// "fromIndex": 1,
// "toIndex": 2
/ /},
/ / {
// "parentNode": {
// "eventStore": {}
/ /},
// "type": "INSERT",
// "toIndex": 3,
// "dom": {}
/ /},
/ / {
// "parentNode": {
// "eventStore": {}
/ /},
// "type": "INSERT",
// "toIndex": 4,
// "dom": {}
/ /},
/ / {
// "parentNode": {
// "eventStore": {}
/ /},
// "type": "REMOVE",
// "fromIndex": 3
/ /}
/ /]
// Here are the four specific changes to the DOM
// if a is matched with a, the old element is assigned directly
/ / b, see the inside of the new element C, have corresponding in old element And assign a seat with old elements lastIndex (2) greater than the current mount seats mountIndex (1), namely, also need not operation
/ B/c, view elements, to find the same old element, but his mountIndex (1) less than lastIndex (2), which need operation, will he move to the back of the lastIndex,
// The first parameter of patch is MOVE,fromIndex represents the last position to be dropped (mountIndex of the new element can be used), toIndex represents the current position of the old element.
Type = INSERT, toIndex = INSERT, toIndex = mountIndex
// e and F are the same as e
// fromIndex represents the position of the current old element. // fromIndex represents the position of the current old element
// MOVE {$$typeof: Symbol(ELEMENT), type: "li", key: "B", ref: undefined, props: {... },... }
// INSERT {$$typeof: Symbol(ELEMENT), type: "li", key: "E", ref: undefined, props: {... }}
// INSERT {$$typeof: Symbol(ELEMENT), type: "li", key: "F", ref: undefined, props: {... }}
// REMOVE {$$typeof: Symbol(ELEMENT), type: "li", key: "D", ref: undefined, props: {... },... }
// 3. Depth-first collection diffQueue implements uniform dom processing
// REMOVE and MOVE are removed before insert, and REMOVE and MOVE together
// INSERT a MOVE into a node
    
class ClassComponent extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      show: true
    }
  }
  
  handleClick = () = > {
    this.setState(state= > ({show: !state.show}));
  }
  render() {
    if(this.state.show){
      return (
        <ul onClick={this.handleClick}>
          <li key='A'>A</li>
          <li key='B'>B</li>
          <li key='C'>C</li>
          <li key='D'>D</li>
        </ul>)}else {
      return (
        <ul onClick={this.handleClick}>
          <li key='A'>A</li>
          <li key='C'>C</li>
          <li key='B'>B</li>
          <li key='E'>E</li>
          <li key='F'>F</li>
        </ul>)}}}Copy the code

life cycle

The initial stage

  • The build instantiation is executedconstructor= >getDerivedStateFromProps= >render= >componentDidMount

Update the stage

  • Accept new datagetDerivedStateFromProps= >shouldComponentUpdate= >render=> Obtain a snapshotgetSnapshotBeforeUpdateThe return value of this is going to be passedcomponentDidUpdateThe third parameter

Destruction of phase

  • componentWillUnMountThis happens when you compare the DIff and if the DOM diff element is missing it offloads the component

Two old life cycles have been removed ~

  • componentWillMount &componentWillUpdate
  • Improper use can cause an endless loop, he can get this to modify the parent component’s data, newgetDerivedStateFromPropsMethod instead of this is a function that can’t get this

context

  • Context is very simple
    • createContextReturn two objectsProviderComponent registration data,ConsumerAccept callback to fetch data
    • Class component, get throughstatic contextType = ThemeContextThe internal component will determine if this exists when it parses the component and if it does, it will putProviderThe passed data hangs on the current instancecontexton
let ThemeContext = React.createContext(null);
/ /... The parent component
<ThemeContext.Provider value={{}}>
    <div>
    </div>
</ThemeContext.Provider>

<ThemeContext.Consumer>
    {
        (value) => (
            <div>
              {value}
            </div>)}</ThemeContext.Consumer>
Copy the code
// Parse the class component
 if(oldElement.type.contextType){
        componentInstance.context = oldElement.type.contextType.Provider.value;
    }
Copy the code
function createContext(defaultValue){
    Provider.value = defaultValue;// Context will copy an initial value
    function Provider(props){
        Provider.value = props.value;// The Provider is reassigned every time it is updated
        return props.children;
    }
    function Consumer(props){
        return onlyOne(props.children)(Provider.value)
    }
    return {Provider, Consumer}
}
Copy the code

fiber(17+)

Screen refresh rate

  • Most devices have screens that are 60 times per second, and the page is drawn in one frame. When the number of frames drawn per second (FPS) reaches 60, the page is a flow and the user feels a block
  • The budget event of each frame is 16.66ms (1 second /60), 1s 60 frames, so the event assigned to each frame is 1000/60 = 16ms, so our code strives not to let the volunteer work exceed 16ms

frame

  • The beginning of each frame includes style calculation, layout and drawing
  • The JS engine and the page rendering engine are in the same render ready,GUI rendering and JS execution are mutually exclusive
  • The browser will delay rendering if a task takes too long
  • If the image is too small, see it on the new page

rAf(requestAnimationFrame)

  • The requestAnimationFrame callback is executed before drawing, which is shown in the figure above in front of layout
  • The following uses manipulation of the DOM before the browser draws to increase its width
<body>
  <div style="background: red; width: 0; height: 20px;"></div>
  <button>start</button>
  <script>
    const div = document.querySelector('div')
    const button = document.querySelector('button')
    let start;
    function progress(){
      div.style.width = div.offsetWidth + 1 + 'px'
      div.innerHTML = div.offsetWidth + The '%'
      if(div.offsetWidth < 100) {let current = Date.now()
        start = current
        timer = requestAnimationFrame(progress)
      }
    }
    button.onclick = function(){
      div.style.width = 0;
      start = Date.now();
      requestAnimationFrame(progress);
    }
  </script>
</body>
Copy the code

requestIdleCallback

  • The requestIdleCallback function is executed when the normal frame task is completed within 16 seconds, indicating that the time is availablerequestIdleCallbackRegistered response in
  • requestIdleCallback(callback,{timeout:1000}),callback receives 2 arguments (didTimeout,timeRemaining())
    • DidTimeout, a Boolean value indicating whether the task times out. Used in conjunction with timeRemaining
    • TimeRemaining (), which represents the timeRemaining in the current frame
    • Timeout: If the task is not executed after the timeout period, the task is forcibly executed

<body>
  <script>
    // 
    function sleep(d){
      for(var t = Date.now();Date.now() - t <= d;){}
    }
    const works = [
      () = >{
        console.log('First mission initiated')
        sleep(20)
        console.log('First mission completed')},() = >{
        console.log('Task Two begins')
        sleep(20)
        console.log('End of second mission')},() = >{
        console.log('Task three begins.')
        sleep(20)
        console.log('End of the third mission')}]// timeout means to tell the browser to execute 1000ms for me even if you don't have free time because I can't wait
    requestIdleCallback(workLoop,{timeout:1000})
    function workLoop(deadLine){
      DidTimeout A Boolean value indicating whether the task has timed out
      // deadline.timeremaining () represents the remaining time of the current frame
      console.log('Time left in this frame'.parseInt(deadLine.timeRemaining()));
      while((deadLine.timeRemaining() > 1 || deadLine.didTimeout) && works.length>0){
        performUnitOfWork()
      }
      if(works.length>0) {console.log(` onlyThe ${parseInt(deadLine.timeRemaining())}Ms, time slice up to wait for the next idle time scheduling ');
        requestIdleCallback(workLoop)
      }
    }

    function performUnitOfWork(){
      works.shift()()
    }
  </script>
</body>
Copy the code

Singly linked lists

  • A singly linked list is a chain-access data structure
  • The data in the linked list is represented by nodes, and each node is composed of: element + pointer (indicating the location of the succeeding element). The element is the storage unit where the data is stored, and the pointer is the address connecting each node

class Update{
  constructor(payload,nextUpdate){
    this.payload = payload
    this.nextUpdate = nextUpdate
  }  
}

class UpdateQueue{
  constructor(){
    this.baseState = null
    this.firstUpdate = null
    this.lastUpdate = null
  }
  enqueueUpdate(update){
    if(this.firstUpdate == null) {this.firstUpdate = this.lastUpdate = update
    }else{
      this.lastUpdate.nextUpdate = update
      this.lastUpdate = update
    }
  }
  forceUpdate(){
      let currentState = this.baseState || {}
      let currentUpdate = this.firstUpdate
      while(currentUpdate){
        let nextState = typeof currentUpdate.payload == 'function'? currentUpdate.payload(currentState) : currentUpdate.payload currentState = {... currentState,... nextState} currentUpdate = currentUpdate.nextUpdate }this.firstUpdate = this.lastUpdate = null;
      this.baseState = currentState
      return currentState
  }
}

let queue = new UpdateQueue();
queue.enqueueUpdate(new Update({name:'sg'}))
queue.enqueueUpdate(new Update({age:12}))
queue.enqueueUpdate(new Update((data) = >({age:data.age+1})))
queue.enqueueUpdate(new Update((data) = >({age:data.age+2})))
console.log(queue.forceUpdate()) ;
console.log(queue.baseState)
Copy the code

DOM=>fiber

  • Why is fiber needed?

Reconcilation before Fiber

  • React recursively compares the VirtualDOM tree to find nodes that need to be changed and then updates them synchronously. This process is calledReconcilation
  • React will tie up browser resources during coordination, causing user-triggered events to go unresponded, and stalling

Fiber

  • Some scheduling policies can be used to allocate CPU resources properly to improve user response data
  • Through Fiber, make your ownReconcilationThe process becomes interruptible. Giving away CPU execution at the right time allows the browser to respond to user interactions in a timely manner
  • Fiber is also an execution unit, and React checks how much time is left after each execution and cedes control if there is no time left

  • Fiber is both a data structure and an object.
    • React currently uses a linked list, with each VirtualDOM node representing an internal Fiber
type Fiber = {
  / / type
  type: any,
  / / the parent node
  return: Fiber,
  // points to the first child node
  child: Fiber,
  // Point to the next brother
  sibling: Fiber
}
Copy the code
  • Here is the DOM compiled with Babel. Each node is converted to a specific data structure (fiber), so the first step is to convert all the DOM to fiber. Fiber has several required properties (Type props return effectTag nextEffect, etc.) that record each DOM information and direct DOM associations
// let element = (
// 
      
//
//
//
// //
// // ) // console.log(JSON.stringify(element, null, 2)) let element = { "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
  • Fiber traversal starts with nodes and has depth first. The diagram shows the association, with child pointing to the element’s child byte, Sibling pointing to the element’s next sibling, and return executing the parent node.beginWorkMethod to implement DOM to fiber, as wellchild return siblingCorrelation between

  • performUnitOfWorkMethod to recursively traverse the DOM, the principle is to find the deepest first element, then find its next sibling element, in order to find the sibling element, find the parent element in order to find the parent element of the sibling element loop through all nodes

The simulated Fiber execution will be discussed in detail later

/ * 1, 2, starting from the vertex traversal, if there is a son, first traverse eldest son * /
// Execute in browser
let A1 = {type:'div'.key:'A1'}
let B1 = {type:'div'.key:'B1'.return:A1};
let B2 = {type:'div'.key:'B2'.return:A1};
let C1 = {type:'div'.key:'C1'.return:B1};
let C2 = {type:'div'.key:'C2'.return:B1};
A1.child = B1;
B1.sibling = B2;
B1.child = C1;
C1.sibling = C2;

function sleep(d){
  for(var t = Date.now();Date.now() - t <= d;){}
}

let nextUnitOfWork = null;// Next execution unit
function workLoop(deadLine){
  while((deadLine.timeRemaining() > 1 || deadLine.didTimeout) && nextUnitOfWork){
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
  if(! nextUnitOfWork){console.log('Render phase execution completed')}else{
    requestIdleCallback(workLoop,{timeout:1000}}})// Start traversal
function performUnitOfWork(fiber){
  beginWork(fiber)/ / fiber processing
  if(fiber.child){// If there are sons, return the eldest
    return fiber.child
  }
  // If there is no son, this fiber is complete
  while(fiber){
    completeUnitOfWork(fiber)
    if(fiber.sibling){
      return fiber.sibling// If you have a brother, return to your own brother
    }
    // I'm looking for my father's brother
    fiber = fiber.return
  }
}
function completeUnitOfWork(fiber){
  console.log('the end',fiber.key)
}
function beginWork(fiber){
  sleep(20)
  console.log('开始',fiber.key)
}
nextUnitOfWork = A1
requestIdleCallback(workLoop,{timeout:1000})
Copy the code

React render process && Fiber three stages

  • ScheduleRoot, Reconcilation, commitRoot
  • Only harmonic is asynchronous

1. ScheduleRoot

  • Here is a demo template
  • Babel compiles the Element to JSX syntax and passes it to render, which wraps the element into a fiber structure for scheduleRoot.
import React from './react';
import ReactDOM from './react-dom';
let style = { border:'3px solid red'.margin:'5px'}
let element = (
  <div id='A1' style={style}>
    A1
    <div id='B1' style={style}>
      B1
      <div id='C1' style={style}>C1</div>
      <div id='C2' style={style}>C2</div>
    </div>
    <div id='B2' style={style}>B2</div>
  </div>
)

ReactDOM.render(
  element,
  document.getElementById('root'))Copy the code
  • 2. Schedule the overall process
  • React can be connected by following three rules
  • A,Rules for traversal
    • Son first, brother second, uncle,
  • B,Completion chain rule
    • Complete yourself after all of your child nodes are complete
  • C,Rules of the effect
    • Complete yourself after all of your child nodes are complete

Harmonic (Reconcilation)

  • Reconcile the process of converting the virtual DOM into Fiber nodes, and the associations between each Fiber, and collect effectLists (which fibers need updating)
  • This phase is asynchronous if the effectList is not recollected at the next time node

The commit phase

  • The COMMIT phase processes the effectList(the DOM that needs to be updated during the mediation phase), where the process C1 starts and ends directly at A1, the reverse of the mediation phase, and ends atrooton
  • Similar to Git branches, fork out a copy from the old tree, add, delete, and update the new branch, and commit it after testing

DOM-DIFF

  • In act17+, dom-diff is the process of generating a new Fiber tree by comparing the old fiber tree to the latest JSX

React optimization principle

  • Only peer nodes are compared, and React does not reuse DOM nodes if they move across hierarchies
  • Different types of elements produce different structures, destroying old structures and creating new ones
  • The element can be identified by a key

A single node

  • If the new child node has only one element and the key and type are different, the old node needs to be marked as deleted and the new fiber node needs to be marked as inserted during the reconciliation phase
  • During the reconciliation phase, old nodes need to be marked as deleted

multi-node

  • If the new node has more than one node, the multiple nodes will undergo a second round of traversal
    • The first round of traversal deals mainly with updates of nodes, including updates of attributes and types
    One by one comparison, all reusable, just update <ul><li key="A">A</li>
        <li key="B">B</li>
        <li key="C">C</li>
        <li key="D">D</li>
        </ul>
        / * * * * * * * * * * * * * /
        <ul>
        <li key="A">A-new</li>
        <li key="B">B-new</li>
        <li key="C">C-new</li>
        <li key="D">D-new</li>
        </ul>One by one, same key,typeDifferent, delete old, add new <ul><li key="A">A</li>
        <li key="B">B</li>
        <li key="C">C</li>
        <li key="D">D</li>
        </ul>
        / * * * * * * * * * * * * * /
        <ul>
        <div key="A">A-new</div>
        <li key="B">B-new</li>
        <li key="C">C-new</li>
        <li key="D">D-new</li>
        </ul>One key different exit round 1 <ul><li key="A">A</li>
        <li key="B">B</li>
        <li key="C">C</li>
        <li key="D">D</li>
        </ul>
        / * * * * * * * * * * * * * /
        <ul>
        <li key="A">A-new</li>
        <li key="C">C-new</li>
        <li key="D">D-new</li>
        <li key="B">B-new</li>
        </ul>
    Copy the code
    • Move (this is the same as diff 16)