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,
ReactDOM
This one mainly provides the Render method, which mounts the React element to the page.React
Is 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 Babel
React.createElement
Syntax (React had to be introduced before version 16 for this reason) - JSX syntax is translated into JS via Babel
babel-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 function
React.createElement
The created virtual DOM is converted into the real DOM and mounted on the second parameter. The core iscreateDOM
function createDOM
It’s sorted by the elements that come inPlain text class native DOM function
The 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.createElement
Used 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 call
setState
He 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 them
shouldUpdate
Update the hook logic on the componentforceUpdate
Forced to update
- Class component is wide to call
forceUpdate
Force updates to components. import { unstable_batchedUpdates } from './react-dom'
Forced to updateunstable_batchedUpdates
The 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 determine
shouldUpdate
Whether the hook is supported and only called if it isforceUpdate
Perform forcible update. Alternatively, we can call it directly from the class componentforceUpdate
Force update - I can walk here
componentWillMount
&getSnapshotBeforeUpdate
&render
&componentDidUpdate
The 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 example
A B C D
andA 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 executed
constructor
= >getDerivedStateFromProps
= >render
= >componentDidMount
Update the stage
- Accept new data
getDerivedStateFromProps
= >shouldComponentUpdate
= >render
=> Obtain a snapshotgetSnapshotBeforeUpdate
The return value of this is going to be passedcomponentDidUpdate
The third parameter
Destruction of phase
componentWillUnMount
This 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, new
getDerivedStateFromProps
Method instead of this is a function that can’t get this
context
- Context is very simple
createContext
Return two objectsProvider
Component registration data,Consumer
Accept callback to fetch data- Class component, get through
static contextType = ThemeContext
The internal component will determine if this exists when it parses the component and if it does, it will putProvider
The passed data hangs on the current instancecontext
on
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 available
requestIdleCallback
Registered 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 called
Reconcilation
- 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 own
Reconcilation
The 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.
beginWork
Method to implement DOM to fiber, as wellchild
return
sibling
Correlation between
performUnitOfWork
Method 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 at
root
on - 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)