State updates for class components rely on the setState method. The setState method takes two forms of parameters: objects — synchronous updates; Function – asynchronous update. This article deals with synchronous update first and asynchronous update finally.
One, prepare knowledge
1. Component status
- Data sources for components: property objects, state objects
- Properties are passed by the parent component
- When the state is internal, the only way to change the state
setState
- Property and state changes affect view updates
- Instead of modifying state directly,
The constructor
Is the only thing you can givethis.state
Where the value is assigned
2. Bind events
React binds events differently than native:
- Property is named for the hump instead of lowercase
- The value is a function reference address, not a string
Second, implementation ideas
1. Implement setState ()
The setState() method is actually called by the instance to get the setState method from the parent Component class, so:
In component.js, add a setState method to the Component class with a partialState parameter to record the state you need to change. The addState method of the updater is called internally and delegated to the updater to update the state.
2. Implement updater-status updates
Updater is essentially a class that inherits Component instances. The component instance is stored internally, and the pendingStates queue is to be updated (because setState is not changed immediately, you need a collection to hold all the states that need to be updated).
- AddState: Takes the state object, stores the state in the array to be updated, and calls the trigger update method.
Create shoudlUpdate method to update the status. The update of state depends on the state of the component instance and the latest state. For reuse purposes, we extract the getState method to calculate the new state to be updated. Updates to the state cause updates to the component, and we call the forceUpdate method of the instance to force the update.
- EmitUpdate: Triggers updates. Call the update component method.
- ShoudlUpdate: Updates the status. Deconstruct an instance of a class
classInstance
And an array of states waiting to take effectpendingStates
, if the state array has a valueshouldUpdate
A method to update the state, passing in an instance of the class and the latest state. - GetState: Gets the new state based on the value of the old state. Deconstruct an instance of a class
classInstance
And an array of states waiting for updatespendingStates
To traverse thependingStates
Retrieves each value to be updated, deconstructing the old state from the instancestate
To merge the status and clear the statuspendingStates
To return toThe merged state
. - ShouldUpdate: The instance and new state of the incoming parameter class. Assign the new state to the old state of the class. Call the forceUpdate method of the Component instance.
Implement forceUpdate– component updates
How do I trigger page updates? Each page corresponds to a div/span, etc. (only the React element can be returned). Therefore, we need to re-call the render method based on the new state, return the new virtual DOM to the new real DOM, and replace the old page structure with the new real DOM
- ForceUpdate: call
render
The createDOM method, which converts the virtual DOM to the real DOM, is defined in react-dom.js, so this step is implemented in react-dom.js. The introduction ofcompareToVdom
Pass in the required three parameters.
- Parent real DOM:
oldDOM.parentNode
- Old virtual DOM: mounted at first
mountClassCompoent
To be added on an instance of the classoldRenderVdom
Property to record the old virtual DOM. And then you can go througholdRenderVdom
Property to get the old virtual DOM corresponding to the component - New virtual DOM: that invokes components
render
Methods to obtain
After rerendering, assign this new virtual DOM to the oldRenderVdom property as the value of the old virtual DOM for the next update.
- CompareToVdom: Compares the old and new virtual DOM and returns the new virtual DOM. The parameter for
parentDOM
Parent real DOM,oldVdom
Old virtual DOM,newVdom
The new virtual DOM. Calling native methodsreplaceChild
Replace the real DOM corresponding to the last virtual DOM with the new DOM
4. Implement event binding
To implement interaction > status updates > page updates, you first need to bind interaction events. Events are passed to the component as props, so we make a judgment in updateProps to bind the events
If the property starts with ‘on’ to indicate that the property is an event handler, take this example: the click event needs to be the dom.onclick = handler.
5. Implement asynchronous update
React source code, setState supports two types of parameter transmission: 1. Objects can be merged by directly expanding their properties; 2. Functions — asynchronous. The function computes the next state based on the previous state and then merges it. So the above code needs to be improved a bit, and also support function arguments.
- GetState: When iterating through each property, it determines whether the property type is
'function'
If so, the state is passed into the function call and the returned state is reassigned tonextState
. It then merges the states, empties the state array to be updated, and so on.
Third, the implementation of class component status update
1. src/index.js
import React from "./react";
import ReactDOM from "./react-dom";
/** * 2. If this.props is used as the parent component, the props can be used as the parent component. The state object is initialized internally and can be changed. The only way to change the state is if setState * React binds events differently than it does natively. The value is not a string but a reference to the address of the function * there is a difference between calling setState and directly changing state * Changing the property or setState will cause the component to refresh and make the view update */
class Counter extends React.Component {
constructor(props) {
super(props);
// The only place to assign a value to state is the constructor
this.state = { number: 0.title: "Counter" };
}
handleClick = (event) = > {
// When changing the state, just pass the updated variable.
// The state this.state is not changed immediately after the setState call, but will be updated after handleClick completes
debugger;
this.setState({ number: this.state.number + 1 });
console.log(this.state);
};
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.handleClick}>+</button>
</div>
);
}
}
ReactDOM.render(<Counter />.document.getElementById("root"));
Copy the code
2. src/component.js
import { compareToVdom } from "./react-dom";
/** updater */
class Updater {
constructor(classInstance) {
// Save the instance
this.classInstance = classInstance;
// An array of states waiting to be updated
this.pendingStates = [];
}
addState(partialState) {
this.pendingStates.push(partialState);
// Trigger the update
this.emitUpdate();
}
emitUpdate() {
this.updateComponent();
}
updateComponent() {
// Deconstruct the state of the instance, waiting for updates
let { classInstance, pendingStates } = this;
if (pendingStates.length > 0) {
shouldUpdate(classInstance, this.getState()); }}/** Get the new state based on the old state and pendingStates */
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance; / / the old state
// For each sub-state ==> merge attributes
pendingStates.forEach((nextState) = >{ state = { ... state, ... nextState }; });// Clear the state array waiting for updates
pendingStates.length = 0;
returnstate; }}function shouldUpdate(classInstance, nextState) {
classInstance.state = nextState; // Assign the new state to the instance's state
classInstance.forceUpdate(); // Force an instance of the class to update
}
class Component {
// Static attributes of the parent can be inherited when subclasses inherit from the parent
// Function components and class components become functions when compiled, so the isReactComponent property is added to distinguish between function components and class components
static isReactComponent = true;
constructor(props) {
this.props = props;
this.state = {}; / / initial value
this.updater = new Updater(this);
}
/** Update the status */
setState(partialState) {
this.updater.addState(partialState);
}
/** Calculates the new virtual DOM to render based on the new attribute state
forceUpdate() {
// Get the old virtual DOM
let oldRenderVdom = this.oldRenderVdom;
// Get the old real DOM
let oldDOM = oldRenderVdom.dom;
// Compute the new real DOM based on the new properties and state
let newRenderVdom = this.render();
compareToVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
// Make the previous new DOM the comparison for the next update
this.oldRenderVdom = newRenderVdom; }}export { Component };
Copy the code
3. src/react.js
import { wrapToVdom } from "./utils";
import { Component } from "./component";
function createElement(type, config, children) {
// Children are always arrays
let ref, key;
if (config) {
delete config.__source; // source: bable attribute generated at compile time
delete config.__self;
ref = config.ref; // ref can be used to refer to the actual DOM element
key = config.key; // Dom-diff optimization is used to uniquely identify a child element
delete config.ref;
delete config.key;
}
letprops = { ... config };if (arguments.length > 3) {
// If there are more than three entries, there are multiple child elements. After interception, save them as an array
props.children = Array.prototype.slice.call(arguments.2).map(wrapToVdom);
} else if (arguments.length === 3) {
props.children = wrapToVdom(children); // React element object or string/number/null/und
}
return {
type,
ref,
key,
props,
};
}
const React = {
createElement,
Component,
};
export default React;
Copy the code
4. react-dom.js
import { REACT_TEXT } from "./constants";
/** * Insert the virtual DOM into the real DOM@param {*} Vdom Virtual DOM/React element *@param {*} Container Real DOM container */
function render(vdom, container) {
mount(vdom, container);
}
/** the page mounts the real DOM */
function mount(vdom, parentDOM) {
// Turn the virtual DOM into the real DOM
let newDOM = createDOM(vdom);
// Append the real DOM to the container
parentDOM.appendChild(newDOM);
}
/** * Turn the virtual DOM into the real DOM *@param {*} Vdom Virtual DOM *@return Real DOM * /
function createDOM(vdom) {
if(! vdom)return null; // null/und is also a valid DOM
let { type, props } = vdom;
let dom; / / true DOM
if (type === REACT_TEXT) {
If the element is text, create a text node
dom = document.createTextNode(props.content);
} else if (typeof type === "function") {> > >if (type.isReactComponent) {
> > > // This is a class component
> > > returnmountClassComponent(vdom); > > >}else {
// Function components
returnmountFunctionComponent(vdom); }}else if (typeof type === "string") {
// Create a DOM node span div p
dom = document.createElement(type);
}
// Handle attributes
if (props) {
// We will implement component and page updates after updating DOM properties.
updateProps(dom, {}, props);
let children = props.children;
// If children is a React element, it is also a virtual DOM
if (typeof children === "object" && children.type) {
// Mount the child virtual DOM to the parent DOM
mount(children, dom);
} else if (Array.isArray(children)) {
reconcileChildren(children, dom);
}
}
vdom.dom = dom; // Add a dom attribute to the virtual DOM to point to the real DOM corresponding to the virtual DOM
return dom;
}
/** Mount class components */
function mountClassComponent(vdom) {
let { type: ClassComponent, props } = vdom;
// Pass the class component's properties to the class component's constructor,
// Create an instance of the class component and return the component instance object
let classInstance = new ClassComponent(props);
// It can be the virtual DOM of a native component, a class component, or a function component
let renderVdom = classInstance.render();
// Add oldRenderVdom=renderVdom to the class instance when the class component is first mounted
classInstance.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
/** Mount the function component */
function mountFunctionComponent(vdom) {
let { type: functionComponent, props } = vdom;
// Get the virtual DOM that the component will render
let renderVdom = functionComponent(props);
return createDOM(renderVdom);
}
/** If the child element is an array, traverse mount to container */
function reconcileChildren(children, parentDOM) {
children.forEach((childVdom) = > mount(childVdom, parentDOM));
}
/** * Update the new attribute to the real DOM *@param {*} Dom Real DOM *@param {*} OldProps Old property object *@param {*} NewProps New property object */
function updateProps(dom, oldProps, newProps) {
for (let key in newProps) {
if (key === "children") {
// Child nodes are handled separately
continue;
} else if (key === "style") {
let styleObj = newProps[key];
for (let attr instyleObj) { dom.style[attr] = styleObj[attr]; } > > >}else if (/^on[A-Z].*/.test(key)) {
> > > // Bind event ==> dom.onclick = event function> > > dom[key.toLowerCase()] = newProps[key]; > > >}else{ dom[key] = newProps[key]; }}for (let key in oldProps) {
// If an attribute exists in the old attribute object, but the new attribute does not, it needs to be deleted
if(! newProps.hasOwnProperty(key)) { dom[key] =null; }}}/ * * *@param {*} ParentDOM Actual DOM *@param {*} OldVdom The old virtual DOM *@param {*} NewVdom New virtual DOM */
export function compareToVdom(parentDOM, oldVdom, newVdom) {
// Get the real DOM corresponding to oldRenderVdom
let oldDOM = oldVdom.dom;
// Get the new real DOM based on the new virtual DOM
let newDOM = createDOM(newVdom);
// Replace the old real DOM with the new real DOM
parentDOM.replaceChild(newDOM, oldDOM);
}
const ReactDOM = {
render,
};
export default ReactDOM;
Copy the code
Four,
In the case of this counter, the event callback is triggered when the plus sign is clicked:
UpdateProps Binding event –> setState Change state –> updater.addState Pass in an array of states that need to be updated –> pendingStates. UpdateComponent updateComponent –> shouldUpdate update state –> getState merge state –> shouldUpdate assign new state –> forceUpdae force update –> CompareToVdom is replaced with a new component