What is VirtualDOM
A Virtual DOM object is a JavaScript representation of a DOM object, which uses JavaScript objects to describe DOM object information
<div className="app">
<h1>Hello</h1>
<p>Hello,world</p>
</div>
Copy the code
The above code is converted to JSX code
{
type: 'div'.props: {
className: 'app'
},
children: [{
type: 'h1'.props: null.children: [{
type: 'text'.props: {
textContent: 'Hello'}}, {type: 'p'.props: null.children: [{
type: 'text'.props: {
textContent: 'Hello,world'}}}}]]Copy the code
Environment set up
npm init -y
Install dependency on WebPack-related and Babel
npm i -D webpack webpack-dev-server webpack-cli @babel/core @babel/preset-env @babel/preset-react babel-loader html-webpack-plugin
Copy the code
Configuration webpack
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
/ * *@type {import ('webpack').Configuration} * /
module.exports = {
mode: 'development'.entry: './src/main.js'.output: {
filename: 'main.bundle.js'.path: path.resolve(__dirname, 'dist')},module: {
rules: [{
test: /\.js(x?) $/,
loader: 'babel-loader'}},devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true.port: 8080.hot: true,},devtool: 'source-map'.plugins: [
new HtmlWebpackPlugin({
title: 'Simple react'.template: './public/index.html'
}),
new webpack.HotModuleReplacementPlugin({})
]
}
Copy the code
Create webpack entry files SRC /main.js, public/index.html
Configuration for Babel, of which the pragma is compiled JSX expression is used to change the function used for the default is the React. The createElement method, which we can view the www.babeljs.cn/docs/babel-…
{
"presets": [
"@babel/preset-env"["@babel/preset-react", {
"pragma": "SpReact.createElement"}}]]Copy the code
Here we just need to configure it to the name of the function we want
The development environment is set up
Create a VirtualDOM object
Create SpReact/index.js and SpReact/createElement.js
SpReact/index.js is used to export all methods on SpReact
import createElement from './createElement'
export default {
createElement
}
Copy the code
SpReact. CreateElement is used to create VirtualDOM objects, which I have replaced with compiling JSX expressions
export default function createElement(type, props, ... children) {
return {
type,
props,
children
}
}
Copy the code
With that in mind, a simple VirtualDOM object has been created, and the method will be refined over time
Print the converted VirtualDOM object in main.js
import SpReact from "./SpReact"
const jsx = (<div><h1>VirtualDOM</h1><div>Create VirtualDOM</div></div>)
console.log(jsx)
Copy the code
JSX after transformation
{
"type": "div"."props": null."children": [{
"type": "h1"."props": null."children": ["VirtualDOM"] {},"type": "div"."props": null."children": ["Create VirtualDOM"]]}}Copy the code
We need to work on that
- Text nodes are converted to objects for the corresponding virtualDOM
- Booleans and NULL are not displayed on the page
- Children can be accessed in props
export default function createElement(type, props, ... children) {
const newChildren = children.reduce((res, child) = > {
if (typeof child === 'string') {
res.push(createElement('text', {
textContent: child
}))
} else if (typeofchild ! = ='boolean'&& child ! = =null) {
res.push(child)
}
return res
}, [])
return {
type,
props: {... props,children: newChildren
},
children: newChildren
}
}
Copy the code
VirtualDOM objects are converted to real DOM objects
To create the Render method, we call the SpReact. Render method to render VirtualDOM onto the page
import diff from "./diff";
export default function render(VirtualDOM, container, oldDOM = container.lastChild) {
diff(VirtualDOM, container, oldDOM)
}
Copy the code
export default function diff(virtualDOM, container, oldDOM) {
if(! oldDOM) { mountElement(virtualDOM, container) } }Copy the code
// Add the created DOM node to the page
function mountElement(virtualDOM, container) {
// Create the current virtualDOM DOM node, iterate over the create child elements, and call mountElement
const dom = createDOM(virtualDOM)
// Add the created DOM node to the page
container.appendChild(dom)
}
// Create the current virtualDOM DOM node, traversing the DOM node where the child element was created
function createDOM(virtualDOM) {
const type = virtualDOM.type
let el
if (type === 'text') {
el = document.createTextNode(virtualDOM.props.textContent)
} else {
el = document.createElement(type)
}
virtualDOM.children.forEach(child= > {
mountElement(child, el)
})
return el
}
Copy the code
Add attributes to DOM elements
const jsx = (<div className="aa"><h1 id="title" onClick={fn}>VirtualDOM</h1><div>Create VirtualDOM</div></div>)
Copy the code
We need to add events to the element node
function createDOM(virtualDOM) {
const type = virtualDOM.type
let el
if (type === 'text') {
el = document.createTextNode(virtualDOM.props.textContent)
} else {
el = document.createElement(type)
// Bind events
updateNodeElement(el,virtualDOM)
}
virtualDOM.children.forEach(child= > {
mountElement(child, el)
})
return el
}
Copy the code
Add properties on props (except children) to the DOM
export default function updateNodeElement(el, virtualDOM) {
const props = virtualDOM.props
Object.keys(virtualDOM.props).forEach(propName= > {
const val = props[propName]
if (propName.startsWith('on')) {
const eventName = propName.slice(2).toLowerCase()
el.addEventListener(eventName, val)
} else if (propName === 'className') {
el.setAttribute('class', val)
} else if(propName ! = ='children') {
el.setAttribute(propName, val)
}
})
}
Copy the code
component
Distinguish between functional components and class components
// Distinguish whether it is a component
export function isComponent(virtualDOM){
return virtualDOM&& typeof virtualDOM.type === 'function'
}
// Check whether it is a function component
export function isFunctionComponent(virtualDOM){
/ / (virtualDOM prototype && virtualDOM. The prototype. The render) is a kind of component,
// Since the class Component has the render method, it inherits from the parent class (Component) even if it is not defined
returnisComponent(virtualDOM)&& ! (virtualDOM.type.prototype && virtualDOM.type.prototype.render) }Copy the code
Start by modifying the previous code
- Check if it is a component and call if it is not
mounNativeElement
- If it is a component
mountComponent
- Determine if it is a function component
- Function component call
buildFunctionComponent
- Class component call
buildClassComponent
- Finally, the virtual DOM call will be returned
mountElement
- There’s no direct call here
mounNativeElement
Because a component might return a component
- There’s no direct call here
function mountElement(virtualDOM, container) {
if (isComponent(virtualDOM)) {
mountComponent(virtualDOM, container)
} else {
mounNativeElement(virtualDOM, container)
}
}
function mounNativeElement(virtualDOM, container) {
const dom = createDOM(virtualDOM)
container.appendChild(dom)
}
function mountComponent(virtualDOM, container) {
let newVirtualDOM
if (isFunctionComponent) {
newVirtualDOM = buildFunctionComponent(virtualDOM)
} else {
newVirtualDOM = buildClassComponent(virtualDOM)
}
mountElement(newVirtualDOM, container)
}
Copy the code
Processing function component
Function component processing is very simple, just call the function component, return the function component return virtualDOM
function buildFunctionComponent(virtualDOM) {
return virtualDOM.type(virtualDOM.props || {})
}
Copy the code
function App(){
return (<div>123</div>)
}
SpReact.render(<App/>.document.getElementById('container'))
Copy the code
Processing class component
You first need to define the parent class of the class component
export default class Component {
constructor(props){
this.props = props
}
render(){}}Copy the code
Returns VirtualDOM of the component’s Render function
function buildClassComponent(virtualDOM) {
const component = new virtualDOM.type(virtualDOM.props)
const newVirtualDOM = component.render()
return newVirtualDOM
}
Copy the code
import SpReact from "./SpReact"
export default class App extends SpReact.Component{
render() {
return (<h1>{this.props.title}</h1>)}}Copy the code
update
const jsx = (<div className="aa"><h1 id="title">VirtualDOM</h1><div>Create VirtualDOM</div></div>)
const jsx1 = (<div className="aa"><h1 id="title">VirtualDOM123</h1><div>Create VirtualDOM123</div></div>)
SpReact.render(jsx, container)
setTimeout(() = > {
SpReact.render(jsx1, container)
},1000)
Copy the code
VitrualDOM object comparison
export default function diff(virtualDOM, container, oldDOM) {
if(! oldDOM) { mountElement(virtualDOM, container) }else {
// VirtualDOM object alignment}}Copy the code
To compare, we need to get the old and new VirtualDOM objects for comparison. Therefore, we need to save the VirtualDOM object when creating the DOM object.
Here you save the VirtualDOM object on the DOM
function createDOM(virtualDOM) {
const type = virtualDOM.type
let el
if (type === 'text') {
el = document.createTextNode(virtualDOM.props.textContent)
} else {
el = document.createElement(type)
updateNodeElement(el, virtualDOM)
}
virtualDOM.children.forEach(child= > {
mountElement(child, el)
})
el.__virtualDOM = virtualDOM
return el
}
Copy the code
Same Node type
Next, handle the same node type
- The current node does not need to be updated; the attribute event of the node needs to be updated
- If the nodes are the same, you need to compare the child nodes
- If a node is redundant, you need to delete it
export default function diff(virtualDOM, container, oldDOM) {
if(! oldDOM) { mountElement(virtualDOM, container) }else {
const oldVirtualDOM = oldDOM.__virtualDOM
// If type is the same, the current node does not need to be updated
if (oldVirtualDOM.type === virtualDOM.type) {
// Update the text
if (virtualDOM.type === 'text') {
updateNodeText(virtualDOM, oldVirtualDOM, oldDOM)
} else {
// Update the events and properties on the node
updateNodeElement(oldDOM, virtualDOM)
}
// Compare the nodes
const oldChildNodes = oldDOM.childNodes
virtualDOM.children.forEach((child, index) = > {
diff(child, oldDOM, oldChildNodes[index])
})
// If a node becomes redundant, delete it
if (oldChildNodes.length > virtualDOM.children.length) {
for (let i = oldChildNodes.length - 1, len = virtualDOM.children.length; i > len - 1; i--) {
unmountElement(oldChildNodes[i])
}
}
}
}
}
Copy the code
// Update the text node
export function updateNodeText(virtualDOM, oldVirtualDOM, oldDOM) {
if (virtualDOM.props.textContent !== oldVirtualDOM.props.textContent) {
oldDOM.textContent = virtualDOM.props.textContent
oldDOM.__virtualDOM = virtualDOM
}
}
// Delete a node
export function unmountElement(el) {
el.remove()
}
Copy the code
Update the events on the element to refine the original updateNodeElement method
export function updateNodeElement(el, virtualDOM, oldVirtualDOM) {
const props = virtualDOM.props || {}
const oldProps = oldVirtualDOM && oldVirtualDOM.props || {}
Object.keys(virtualDOM.props).forEach(propName= > {
const val = props[propName]
const oldVal = oldProps[propName]
if (val === oldVal) {
return
}
if (propName.startsWith('on')) {
const eventName = propName.slice(2).toLowerCase()
el.addEventListener(eventName, val)
if (oldVal) {
el.removeEventListener(eventName, oldVal)
}
} else if (propName === 'className') {
el.setAttribute('class', val)
} else if(propName ! = ='children') {
el.setAttribute(propName, val)
}
})
// Remove properties that exist in old VirtualDOM objects but do not exist in new VirtualDOM objects
Remove unnecessary events and attributes from the DOM
Object.keys(oldProps).forEach(oldPropName= > {
const oldVal = oldProps[oldPropName]
const val = props[oldPropName]
if(! val) {if (oldPropName.startsWith('on')) {
const oldEventName = oldPropName.slice(2).toLowerCase()
el.removeEventListener(oldEventName, oldVal)
} else if (oldPropName === 'className') {
el.removeAttribute('class', oldVal)
} else if(propName ! = ='children') {
el.removeAttribute(oldPropName, oldVal)
}
}
})
}
Copy the code
The node type is different
Nodes of different types are not components. Create a new VirtualDOM and replace the original DOM element
export default function diff(virtualDOM, container, oldDOM) {
if(! oldDOM) { mountElement(virtualDOM, container) }else {
const oldVirtualDOM = oldDOM.__virtualDOM
if (oldVirtualDOM.type === virtualDOM.type) {
if (virtualDOM.type === 'text') {
updateNodeText(virtualDOM, oldVirtualDOM, oldDOM)
} else {
updateNodeElement(oldDOM, virtualDOM, oldVirtualDOM)
}
} else if(! isComponent(virtualDOM)) {const el = createDOM(virtualDOM)
container.replaceChild(el, oldDOM)
}
const oldChildNodes = oldDOM.childNodes
virtualDOM.children.forEach((child, index) = > {
diff(child, oldDOM, oldChildNodes[index])
})
if (oldChildNodes.length > virtualDOM.children.length) {
for (let i = oldChildNodes.length - 1, len = virtualDOM.children.length; i > len - 1; i--) {
unmountElement(oldChildNodes[i])
}
}
}
}
Copy the code
Component updates
SetState update
You need to declare the setState method on the parent of the child component, change the state value of the component, call the render method of the component to produce a new VirtualDOM object, and then update the page DOM element by comparing the old and new VirtualDOM objects
export default class Component {
constructor(props) {
this.props = props
}
setState(state) {
this.state = { ... this.state, ... state }// Generate a new VirtualDOM object
const virtualDOM = this.render()
// Get the old DOM object
const oldDOM = this.getDOM()
// Add the Component property to virtualDOM
virtualDOM.component = this
/ / than
diff(virtualDOM, oldDOM.parentNode, oldDOM)
}
setDOM(dom) {
this.__DOM = dom
}
getDOM() {
return this.__DOM
}
render(){}Copy the code
Save the component to a new VirtualDOM object
function buildClassComponent(virtualDOM) {
const component = new virtualDOM.type(virtualDOM.props)
const newVirtualDOM = component.render()
newVirtualDOM.component = component
return newVirtualDOM
}
Copy the code
After creating the VirtualDOM object’s DOM element, you need to save the component’s DOM element to the component if it is the corresponding VirtualDOM object.
function mounNativeElement(virtualDOM, container) {
const dom = createDOM(virtualDOM)
container.appendChild(dom)
const component = virtualDOM.component
if (component) {
component.setDOM(dom)
}
}
Copy the code
Check whether components are the same node
SpReact.render(<App title="App"/>, container)
setTimeout(() = > {
SpReact.render(<App title="App1"/>, container)
},1000)
Copy the code
If the new VirtualDOM object is a component
export default function diff(virtualDOM, container, oldDOM) {
if(! oldDOM) { mountElement(virtualDOM, container) }else {
const oldVirtualDOM = oldDOM.__virtualDOM
if (oldVirtualDOM.type === virtualDOM.type) {
if (virtualDOM.type === 'text') {
updateNodeText(virtualDOM, oldVirtualDOM, oldDOM)
} else {
updateNodeElement(oldDOM, virtualDOM, oldVirtualDOM)
}
} else if(! isComponent(virtualDOM)) {const el = createDOM(virtualDOM)
container.replaceChild(el, oldDOM)
} else {
diffComponent(virtualDOM, oldVirtualDOM, oldDOM, container)
}
const oldChildNodes = oldDOM.childNodes
virtualDOM.children.forEach((child, index) = > {
diff(child, oldDOM, oldChildNodes[index])
})
if (oldChildNodes.length > virtualDOM.children.length) {
for (let i = oldChildNodes.length - 1, len = virtualDOM.children.length; i > len - 1; i--) {
unmountElement(oldChildNodes[i])
}
}
}
}
Copy the code
Check whether it is the same component
export function isSameComponent(virtualDOM,oldVirtualDOM){
const oldComponent = oldVirtualDOM.component
return oldComponent && virtualDOM.type === oldComponent.constructor
}
Copy the code
If the old and new VirtualDOM are not the same component, create the DOM corresponding to the node of the new VirtualDOM object and delete the oldDOM
function diffComponent(virtualDOM, oldVirtualDOM, oldDOM, container) {
if (isSameComponent(virtualDOM, oldVirtualDOM)) {
updateComponent(virtualDOM, oldVirtualDOM, oldDOM, container)
} else {
// There is no DOM element created directly,
// This is because there is a lot of logic in creating a component's CORRESPONDING DOM element, such as saving the Component to a VirtualDOM object
// The original logic can be improved, reuse the previous logic
mountElement(virtualDOM, container, oldDOM)
}
}
Copy the code
After mountElement, mounNativeElement is eventually called, where the oldDOM object is removed from the mounNativeElement
export function mountElement(virtualDOM, container, oldDOM) {
if (isComponent(virtualDOM)) {
mountComponent(virtualDOM, container, oldDOM)
} else {
mounNativeElement(virtualDOM, container, oldDOM)
}
}
export function mountComponent(virtualDOM, container, oldDOM) {
let newVirtualDOM
if (isFunctionComponent(virtualDOM)) {
newVirtualDOM = buildFunctionComponent(virtualDOM)
} else {
newVirtualDOM = buildClassComponent(virtualDOM)
}
mountElement(newVirtualDOM, container, oldDOM)
}
export function mounNativeElement(virtualDOM, container, oldDOM) {
const dom = createDOM(virtualDOM)
container.appendChild(dom)
if (oldDOM) {
unmountElement(oldDOM)
}
const component = virtualDOM.component
if (component) {
component.setDOM(dom)
}
}
Copy the code
If it is the same component, update the props of the component and regenerate the virtualDOM object for comparison
export function updateComponent(virtualDOM, oldVirtualDOM, oldDOM, container) {
const oldComponent = oldVirtualDOM.component
// Update the props of the component
oldComponent.updateProps(virtualDOM.props)
const nextVirtualDOM = oldComponent.render()
nextVirtualDOM.component = oldComponent
diff(nextVirtualDOM, container, oldDOM)
}
Copy the code
To call the lifecycle function, first add the lifecycle function to the Component class
export function updateComponent(virtualDOM, oldVirtualDOM, oldDOM, container) {
const oldComponent = oldVirtualDOM.component
oldComponent.componentWillReceiveProps()
let props = virtualDOM.props
let oldProps = oldVirtualDOM.props
if (oldComponent.shouldComponentUpdate(props, oldProps)) {
oldComponent.componentWillUpdate(props)
oldComponent.updateProps(virtualDOM.props)
const nextVirtualDOM = oldComponent.render()
nextVirtualDOM.component = oldComponent
diff(nextVirtualDOM, container, oldDOM)
oldComponent.componentDidUpdate(oldProps)
}
}
Copy the code
The ref attribute
<h1 ref={title= > this.title = title}>{this.props.title}</h1>
Copy the code
When a DOM element is created, the function corresponding to the ref attribute is executed
export function createDOM(virtualDOM) {
const type = virtualDOM.type
let el
if (type === 'text') {
el = document.createTextNode(virtualDOM.props.textContent)
} else {
el = document.createElement(type)
updateNodeElement(el, virtualDOM)
}
virtualDOM.children.forEach(child= > {
mountElement(child, el)
})
if (virtualDOM && virtualDOM.props && virtualDOM.props.ref) {
virtualDOM.props.ref(el)
}
el.__virtualDOM = virtualDOM
return el
}
Copy the code
Add a ref attribute to the component, and the value of the ref attribute is the component
<Alert ref={alert= > this.alert = alert}/>
Copy the code
export function mountComponent(virtualDOM, container, oldDOM) {
let newVirtualDOM
if (isFunctionComponent(virtualDOM)) {
newVirtualDOM = buildFunctionComponent(virtualDOM)
} else {
newVirtualDOM = buildClassComponent(virtualDOM)
}
mountElement(newVirtualDOM, container, oldDOM)
const component = newVirtualDOM.component
// If it is a component, the component lifecycle function is called
If the ref attribute is present in the props, the function corresponding to the ref attribute is called
if(component){
component.componentDidMount()
if (component.props && component.props.ref) {
component.props.ref(component)
}
}
}
Copy the code
Key attribute alignment
Children of the same parent node can compare VirtualDOM objects using the key attribute
export default function diff(virtualDOM, container, oldDOM) { if (! oldDOM) { mountElement(virtualDOM, container) } else { const oldVirtualDOM = oldDOM.__virtualDOM if (oldVirtualDOM.type=== virtualDOM.type) {
if (virtualDOM.type === 'text') {
updateNodeText(virtualDOM, oldVirtualDOM, oldDOM)
} else {
updateNodeElement(oldDOM, virtualDOM, oldVirtualDOM)
}
- const oldChildNodes = oldDOM.childNodes
- virtualDOM.children.forEach((child, index) => {
- diff(child, oldDOM, oldChildNodes[index])
-})// compare the child nodes+ diffChildren(virtualDOM, oldDOM)
- if (oldChildNodes.length > virtualDOM.children.length) {
- for (let i = oldChildNodes.length - 1, len = virtualDOM.children.length; i > len - 1; i--) {
- unmountElement(oldChildNodes[i])
-}
-}} else if (! isComponent(virtualDOM)) {- const el = createDOM(virtualDOM)
- container.replaceChild(el, oldDOM)// The virtualDOM object may have been created by the component. // mounNativeElement will be called to handle the component's logic+ mountElement(virtualDOM, container, oldDOM)
} else {
diffComponent(virtualDOM, oldVirtualDOM, oldDOM, container)
}
}
}
Copy the code
- First, check whether there is a key attribute on the old node. If there is no key attribute, use the previous logic (loop call diff for comparison).
- There is a key attribute
- Use the key attribute in the new virtualDOM to find the old node, and if it exists, determine whether the location is correct or not, and move the old node to the correct location
- Create the corresponding virtualDOM if it does not exist
function diffChildren(virtualDOM, oldDOM) {
const oldChildNodes = oldDOM.childNodes
let keyElements = {}
for (let i = 0, len = oldChildNodes.length; i < len; i++) {
const el = oldChildNodes[i]
if (el.nodeType === 1) {
const key = el.getAttribute('key')
if (key) {
keyElements[key] = el
}
}
}
const hasNoKey = Object.keys(keyElements).length === 0
if (hasNoKey) {
virtualDOM.children.forEach((child, index) = > {
diff(child, oldDOM, oldChildNodes[index])
})
} else {
// There is a key attribute
virtualDOM.children.forEach((child, index) = > {
const key = child.props.key
if (key) {
const el = keyElements[key]
if (el) {
// Move the oldChilNodes position
if(el ! == oldChildNodes[index]) oldDOM.insertBefore(el, oldChildNodes[index])/ / than
else diff(child, oldDOM, el)
} else {
// Create it again
mountElement(child, oldDOM, oldChildNodes[index], true)}}})}if (oldChildNodes.length > virtualDOM.children.length) {
for (let i = oldChildNodes.length - 1, len = virtualDOM.children.length; i > len - 1; i--) {
unmountElement(oldChildNodes[i])
}
}
}
Copy the code
MountElement adds a parameter to whether it is new. If it is new, oldDOM will not be deleted when mounNativeElement is called. When inserting an element, you need to insert the new element before oldDOM
export function mountElement(virtualDOM, container, oldDOM, isNew) {
if (isComponent(virtualDOM)) {
mountComponent(virtualDOM, container, oldDOM, isNew)
} else {
mounNativeElement(virtualDOM, container, oldDOM, isNew)
}
}
export function mounNativeElement(virtualDOM, container, oldDOM, isNew) {
const dom = createDOM(virtualDOM)
if (oldDOM) {
// Insert the element before the oldDOM element
container.insertBefore(dom, oldDOM)
if(! isNew) { unmountElement(oldDOM) } }else {
container.appendChild(dom)
}
const component = virtualDOM.component
if (component) {
component.setDOM(dom)
}
}
export function mountComponent(virtualDOM, container, oldDOM, isNew) {
let newVirtualDOM
if (isFunctionComponent(virtualDOM)) {
newVirtualDOM = buildFunctionComponent(virtualDOM)
} else {
newVirtualDOM = buildClassComponent(virtualDOM)
}
mountElement(newVirtualDOM, container, oldDOM, isNew)
const component = newVirtualDOM.component
if (component) {
component.componentDidMount()
if (component.props && component.props.ref) {
component.props.ref(component)
}
}
}
Copy the code
The key property deletes a node
If the key attribute is not present, the previous index is used to remove unnecessary elements
If the key attribute exists, you need to traverse the old node and use the key of the old node to search for the element corresponding to the key attribute in the new node. If the new node does not exist, the current node has been deleted
function diffChildren(virtualDOM, oldDOM) {
const oldChildNodes = oldDOM.childNodes
let keyElements = {}
for (let i = 0, len = oldChildNodes.length; i < len; i++) {
const el = oldChildNodes[i]
if (el.nodeType === 1) {
const key = el.getAttribute('key')
if (key) {
keyElements[key] = el
}
}
}
const hasNoKey = Object.keys(keyElements).length === 0
if (hasNoKey) {
// Compare nodes
virtualDOM.children.forEach((child, index) = > {
diff(child, oldDOM, oldChildNodes[index])
})
// Delete a node
if (oldChildNodes.length > virtualDOM.children.length) {
for (let i = oldChildNodes.length - 1, len = virtualDOM.children.length; i > len - 1; i--) {
unmountElement(oldChildNodes[i])
}
}
} else {
const newKeyElements = {}
virtualDOM.children.forEach((child, index) = > {
const key = child.props.key
if (key) {
newKeyElements[key] = child
const el = keyElements[key]
if (el) {
if(el ! == oldChildNodes[index]) oldDOM.insertBefore(el, oldChildNodes[index])else diff(child, oldDOM, el)
} else {
mountElement(child, oldDOM, oldChildNodes[index], true)}}})for (let i = 0; i < oldChildNodes.length; i++) {
let oldChild = oldChildNodes[i]
let oldKey = oldChild.__virtualDOM.props.key
if(! newKeyElements[oldKey]) { unmountElement(oldChild) } } } }Copy the code
Remove nodes
Deleting a node also requires consideration of whether the node is a node or text
- Text, delete the node directly
- Component, which calls the life cycle function that unloads the component
- If the deleted node has the ref attribute, the ref attribute needs to be deleted
- If there are events, delete the events of the node
export function unmountElement(el) {
const virtualDOM = el.__virtualDOM
if (virtualDOM.type === 'text') {
el.remove()
return
}
// Not text
const component = virtualDOM.component
// Component lifecycle
if (virtualDOM.component) {
component.componentWillMount()
}
const props = virtualDOM.props
// Clear the ref attribute
if (props && props.ref) {
props.ref(null)}// Delete the event
Object.keys(props).forEach(propName= > {
if (propName.startsWith('on')) {
const event = propName.slice(2).toLowerCase()
const val = virtualDOM.props[propName]
el.removeEventListener(event, val)
}
})
// Delete the child node
if (el.childNodes.length) {
el.childNodes.forEach(child= > {
unmountElement(child)
})
}
// Delete the current element
el.remove()
}
Copy the code