This series of articles is to share my own step-by-step process of writing the entire framework, and those interested in XDM can refer to the source code. Git repository: github.com/sullay/art-…
Data update
Through the previous efforts our framework has been able to complete the page rendering work, the data update actually only need to change the data of the component can be updated.
To review the previous code, for a vNode object, the DOM is regenerated if there is no $DOM attribute.
getDom() {
if (!this.$dom) this.createDom();
return this.$dom;
}
Copy the code
So we just need to leave the original $DOM empty after the data update and then regenerate it in subsequent renders. Here we have the convention that the Component’s data property is used to hold all properties that support dynamic updates.
// Pass in data that needs to be updated to trigger rendering
setData(data) {
for (const key in data) {
this.data[key] = data[key];
}
let oldDom = this.$vNode.$dom;
// The $dom of the custom node component points to the $dom of the child node, which is set to null to trigger createDom
this.$vNode.$dom = null;
// Pass oldDom as a replacement
renderDomTree(this.$vNode, this.$vNode.$parentNode, oldDom);
}
Copy the code
The update is done by simply emptying $DOM, but there are some problems:
- All dom of the custom component needs to be recreated
- The initialized state of the child component is not preserved
$dom and $instance reuse
To solve these two problems, we need to create the dom as much as possible when creating the DOM. For vComponentNode, we need to replace the $instance attribute of the new node with the $instance attribute of the old node.
/ / vComponentNode class
createDom() {
let preChildren = this.$children;
let child = this.$instance.render();
this.$children = [child];
// Reuse the previous DOM or subcomponent through diff each time you create it
vNode.diffDom(this.$children, preChildren);
this.$dom = child.getDom();
}
// The DOM in the old node is reassigned according to the new node
static updateDom(newNode, preNode) {
let dom = preNode.$dom;
for (let key in preNode.$props) dom[key] = null;
for (let event in preNode.$events) dom.removeEventListener(event, preNode.$events[event]);
// Remove the child nodes and organize the DOM structure when renderDomTree is called
dom.innerHTML = null;
// Set the properties
for (let key in newNode.$props) dom[key] = newNode.$props[key];
// Listen on events
for (let event in newNode.$events) dom.addEventListener(event, newNode.$events[event]);
newNode.$dom = dom;
}
// Use existing DOM or custom components as much as possible
static diffDom(newNodes = [], preNodes = []) {
if(! newNodes.length || ! preNodes.length)return;
// All old nodes
let preMap = new Map(a);for (const node of preNodes) {
if(! preMap.has(node.$type)) preMap.set(node.$type, []);if (node.$dom) preMap.get(node.$type).push(node);
}
// Iterate over the new node to see if there are any nodes that can be reused
for (const node of newNodes) {
if (preMap.has(node.$type) && preMap.get(node.$type).length) {
let preNode = preMap.get(node.$type).shift();
if (vComponentNode.isVNode(node)) {
let preChildren = preNode.$children;
// Custom components, reuse old component instances
node.$instance = preNode.$instance;
// The component instance $vNode points to the new node
node.$instance.$vNode = node;
// Update the custom component virtual DOM tree
let child = node.$instance.render();
node.$children = [child];
vNode.diffDom(node.$children, preChildren);
// The custom component $dom points to the child component's $dom
node.$dom = child.$dom;
} else {
// Regular node node, continue to diff child components after updatevNode.updateDom(node, preNode); vNode.diffDom(node.$children, preNode.$children); }}}}Copy the code
Currently, the above code only supports reuse at the same level. You are advised to use the specified key value for reuse at different levels.
Props and Events updated
At this point we can reuse the PREVIOUS DOM and component instances, but there is a hidden bug. Let’s review the code from the previous article.
export class vComponentNode extends vNode {
constructor(type = ' ', allProps = {}, slots = []){...this.$instance = new type(this.$props, this.$events, slots);
this.$instance.$vNode = this; }}Copy the code
Assuming that we have a parent component, we want the props property of the child component to change based on the parent component’s data property. The props property of the subcomponent was passed in when the component object was constructed, and we reused the original $instance property in the update, so the props property could not be updated.
We could consider using something similar to react, with an extra hook function to trigger an update to the props. I used the other approach here.
export class vComponentNode extends vNode {
constructor(type = ' ', allProps = {}, slots = []) {
super(type, allProps);
// Identify custom components
this.$isComponent = true;
/ / slots
this.$slots = slots;
// Create a component instance
this.$instance = new type(this); }}// Customize the component class
export class Component {
constructor(node) {
// Bind the corresponding node
this.$vNode = node;
this.data = {}
}
// All attributes are retrieved from Node. Data and events can be updated without initialization when reusing the original component.
get props() {
return this.$vNode.$props;
}
get events() {
return this.$vNode.$events;
}
get slots() {
return this.$vNode.$slots; }}Copy the code
Instead of passing the props property to the component class, we pass the vNode object so that the props property is still bound to the vNode object. When a component is updated, $instance is reused, but the props property can still be updated.
So far we have completed the data update work, and in the next chapter we will start writing the framework’s task scheduling, which is the core function of the framework.
XDM think this series of articles are helpful to you!!