In our work, we have been using React and its related framework (ANTD/ANTD-Mobile).
However, there is a lack of understanding of react’s deeper aspects: how does JSX syntax relate to the virtual DOM? How are higher-order components implemented? How does Dom Diff work?
Check the gaps by writing a React brochure.
JSX and the virtual DOM
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<label className="test" htmlFor='hello'>
hello<span>world</span>
</label>,
document.getElementById('root')
);
Copy the code
Using reactdom.render, passing the first argument to JSX syntax sugar and the second argument to Container, it is easy to create an H1 DOM node on a document.
In fact, the internal execution is as follows:
import React from 'react';
import {render} from 'react-dom';
render(
React.createElement(
'label',
{htmlFor:'hello',className:'test'},
'hello',
React.createElement(
'span',
null,
'world'
)
),
document.getElementById('root')
);
Copy the code
So when ReactDOM. Render came in, it looked like the React method didn’t work, but it had to because the React createElement method was used.
Render out HTML:
<label for="hello" class="test">hello<span>world</span></label>
Copy the code
To see how react createElement works, look at the source code:
var React = { ... The createElement method: createElementWithValidation, / / method defines the createElement method on the React... } function createElementWithValidation(type, props, children) { var element = createElement.apply(this, arguments); . // Check if the iterator array has a unique key... // Check fragment props... //props type check return Element} function createElement(type, config, children) {var propName = void 0; // Reserved names are extracted var props = {}; var key = null; var ref = null; var self = null; var source = null; . return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); }Copy the code
The react. createElement command is executed to verify the props and types parameters. The childrens parameters are type, props, key, ref, self, and source. Returns a nested object similar to the Babel syntax tree structure. , as shown below:
We keep the most critical properties of the returned object (type, props) and simplify the createElement method to make it easier to understand:
function ReactElement(type,props) { this.type = type; this.props = props; } let React = { createElement(type,props={},... childrens){ childrens.length===1? childrens = childrens[0]:void 0 return new ReactElement(type,{... props,children:childrens}) } };Copy the code
React.createelement returns an Object containing the type tag, its tag attribute, and children, passed as arguments to the reactdom.render () method
{
props:{
childrens:['text',{type:'xx',props:{}}]
name:'xx'
className:'xx'
}
type:'xx'
}
Copy the code
We can then abbreviate the implementation method based on the reactdom.render () input.
let render = (vNode,container)=>{ let {type,props} = vNode; let elementNode = document.createElement(type); For (let attr in props){// Loop through all properties if(attr === 'children'){// If (typeof props[attr] == ForEach (item=>{if(typeof item= == 'object'){// If (typeof item= == 'object'){// If (typeof item= == 'object'){ Elementnode.appendchild (document.createTextNode(item)); Elementnode.appendchild (document.createTextNode(props[attr])); } }else{ elementNode = setAttribute(elementNode,attr,props[attr]) } } container.appendChild(elementNode) }; function setAttribute(dom,name,value) { if(name === 'className') name = 'class' if(/on\w+/.test(name)){ name = name.toLowerCase(); dom[ name ] = value || ''; }else if ( name === 'style' ) { if ( ! value || typeof value === 'string' ) { dom.style.cssText = value || ''; } else if ( value && typeof value === 'object' ) { for ( let name in value ) { dom.style[ name ] = typeof value[ name ] === 'number' ? value[ name ] + 'px' : value[ name ]; } } }else{ if ( name in dom ) { dom[ name ] = value || ''; } if ( value ) { dom.setAttribute( name, value ); } else { dom.removeAttribute( name ); } } return dom }Copy the code
dom diff
Ract acts as a framework for data rendering DOM, and the traditional way of deleting an entire node and creating a new one can be very performance consuming.
React renders the page in a way that compares the changes before and after the virtual DOM to create a new DOM.
To check whether a node has changed, we need to compare the node with its parent nodes, so the time complexity of finding the minimum number of changes between any two trees is O(n^3).
React compares only the current layer (the same color layer), optimizing the comparison step to be close to O(n).
Create the DOM
Optimize the createElement method in JSX and the virtual DOM:
element.js
let utils = require('./utils') class Element { constructor(tagName, attrs, key, children) { this.tagName = tagName; this.attrs = attrs; this.key = key; this.children = children || []; } render() { let element = document.createElement(this.tagName); for (let attr in this.attrs) { utils.setAttribute(element, attr, this.attrs[attr]); element.setAttribute('key', this.key) } let children = this.children || []; ForEach (child => {let childElement = (child instanceof Element)? child.render() : document.createTextNode(child); element.appendChild(childElement); }); return element; }}Copy the code
By extension (order traversal first)
class Tree {
constructor(v, children) {
this.v = v
this.children = children || null
}
}
const tree = new Tree(10, [
new Tree(5),
new Tree(3, [new Tree(7), new Tree(11)]),
new Tree(2)
])
module.exports = tree
Copy the code
const tree = require('./1.Tree')
function tree_transverse(tree) {
console.log(tree.v)//10 5 3 7 11 2
tree.children && tree.children.forEach(tree_transverse)
}
tree_transverse(tree)
Copy the code
Create the original DOM Dom1 and insert it into the page.
let ul1 = createElement('ul', {class: 'list'}, 'A', [
createElement('li', {class: 'list1'}, 'B', ['1']),
createElement('li', {class: 'list2'}, 'C', ['2']),
createElement('li', {class: 'list3'}, 'D', ['3'])
]);
let root = dom1.render();
document.body.appendChild(root);
Copy the code
Create the DOM tree dom2 of node changes, modify the attribute class of the parent node ul of Dom2, add and modify the location of child nodes
let ul2 = createElement('ul', {class: 'list2'}, 'A', [
createElement('li', {class: 'list4'}, 'E', ['6']),
createElement('li', {class: 'list1'}, 'B', ['1']),
createElement('li', {class: 'list3'}, 'D', ['3']),
createElement('li', {class: 'list5'}, 'F', ['5']),
]);
Copy the code
We can’t just destroy dom1 and create dom2. Instead, you should compare the old and new DOM and add, delete and change the original DOM.
let patches = diff(dom1, dom2,root)
Copy the code
- It starts with two nodes
Text node
To compare
function diff(oldTree, newTree, root) { let patches = {}; let index = 0; walk(oldTree, newTree, index, patches, root); return patches; } function walk(oldNode, newNode, index, patches, root) { let currentPatch = []; if (utils.isString(oldNode) && utils.isString(newNode)) { if (oldNode ! = newNode) { currentPatch.push({type: utils.TEXT, content: newNode}); }}}Copy the code
If the text is different, we patch it, record the type of modification and the text content
- Tag comparison: If the tags are consistent, compare the attributes. If no, the node is replaced. Record the replacement patch
·· Else if (oldNode.tagName == newNode.tagName) {let attrsPatch = diffAttrs(oldNode, newNode); if (Object.keys(attrsPatch).length > 0) { currentPatch.push({type: utils.ATTRS, node: attrsPatch}); } } else { currentPatch.push({type: utils.REPLACE, node: newNode}); }...Copy the code
- Modify the original DOM node according to the patch
let keyIndex = 0; let utils = require('./utils'); let allPatches; Let {Element} = require('./ Element ') function patch(root, patches) {allPatches = patches; walk(root); } function walk(node) { let currentPatches = allPatches[keyIndex++]; (node.childNodes || []).forEach(child => walk(child)); if (currentPatches) { doPatch(node, currentPatches); } } function doPatch(node, currentPatches) { currentPatches.forEach(patch=> { switch (patch.type) { case utils.ATTRS: for (let attr in patch.node) { let value = patch.node[attr]; if (value) { utils.setAttribute(node, attr, value); } else { node.removeAttribute(attr); } } break; case utils.TEXT: node.textContent = patch.content; break; case utils.REPLACE: let newNode = (patch.node instanceof Element) ? patch.node.render() : document.createTextNode(patch.node); node.parentNode.replaceChild(newNode, node); break; case utils.REMOVE: node.parentNode.removeChild(node); break; } }) } module.exports = patchCopy the code
At this point, we are done tinkering with the parent node.
For ul child nodes, we can iterate once using the same method. However, we recommend using the child node’s key to determine whether to delete, add, or change order more quickly.
In oldTree, there are three child elements B, C, D and in newTree, there are four child elements E, B, C, D
- Remove elements in oldTree that are not in newTree
function childDiff(oldChildren, newChildren) { let patches = [] let newKeys = newChildren.map(item=>item.key) let oldIndex = 0; while (oldIndex < oldChildren.length) { let oldKey = oldChildren[oldIndex].key; //A if (! newKeys.includes(oldKey)) { remove(oldIndex); oldChildren.splice(oldIndex, 1); } else { oldIndex++; }} // index function remove(index) {patches. Push ({type: utils.remove, index})}Copy the code
- Merge newTree array into oldTree. Merge new to old, equal old displacement, record new bitmarks.
function childDiff(oldChildren, newChildren) { ... oldIndex = 0; newIndex = 0; while (newIndex < newChildren.length) { let newKey = (newChildren[newIndex] || {}).key; let oldKey = (oldChildren[oldIndex] || {}).key; if (! oldKey) { insert(newIndex,newChildren[newIndex]); newIndex++; } else if (oldKey ! = newKey) { let nextOldKey = (oldChildren[oldIndex + 1] || {}).key; if (nextOldKey == newKey) { remove(newIndex); oldChildren.splice(oldIndex, 1); } else { insert(newIndex, newChildren[newIndex]); newIndex++; } } else { oldIndex++; newIndex++; } } function remove(index) { patches.push({type: utils.REMOVE, index}) } ...Copy the code
- Deleting Redundant Nodes
while (oldIndex++ < oldChildren.length) {
remove(newIndex)
}
Copy the code
- Modify nodes based on patches
function childPatch(root, patches = []) { let nodeMap = {}; (Array.from(root.childNodes)).forEach(node => { nodeMap[node.getAttribute('key')] = node }); patches.forEach(path=> { let oldNode switch (path.type) { case utils.INSERT: let newNode = nodeMap[path.node.key] || path.node.render() oldNode = root.childNodes[path.index] if (oldNode) { root.insertBefore(newNode, oldNode) } else { root.appendChild(newNode) } break; case utils.REMOVE: oldNode = root.childNodes[path.index] if (oldNode) { root.removeChild(oldNode) } break; Default: throw new Error(' there is no such patch type ')}}Copy the code
Record the patch modification result:
Detailed DOM-diff address
High order component
(To be continued)
Recruitment is stuck
Bytedance is hiring!
Position Description: Front-end Development (senior) ToB Direction — Video Cloud (Base: Shanghai, Beijing)
1. Responsible for the productization of multimedia services such as voD/live broadcast/real-time communication and the construction of business cloud platform;
2. Responsible for the construction and system development of multimedia quality system, operation and maintenance system;
3, good at abstract design, engineering thinking, focus on interaction, to create the ultimate user experience.
Job requirements
1. Major in Computer, communication and electronic information science is preferred;
2, familiar with all kinds of front end technology, including HTML/CSS/JavaScript/Node. Js, etc.
3. In-depth knowledge of JavaScript language and use of React or vue.js and other mainstream development frameworks;
4. Familiar with Node.js, Express/KOA and other frameworks, experience in developing large server programs is preferred;
5. Have some understanding of user experience, interactive operation and user demand analysis, experience in product or interface design is preferred;
6, own technical products, open source works or active open source community contributors are preferred.
Position window
Relying on the audio and video technology accumulation and basic resources of douyin, Watermelon video and other products, the video Cloud team provides customers with the ultimate one-stop audio and video multimedia services, including audio and video on demand, live broadcasting, real-time communication, picture processing, etc. Internally as the video technology center, service internal business; External to create productized audio and video multimedia service solutions, serving enterprise users.
The team has standardized project iteration process and perfect project role configuration; Strong technical atmosphere, embrace open source community, regular sharing, so that everyone can grow with the rapid business, with technology to change the world!
The delivery way
You can send your resume directly to: [email protected]
You can also scan the push two-dimensional code online delivery, looking forward to your participation! ~