Vue dumb learning principle: rendering model

This is not the source code
of Vue This is not the source code of Vue This is not the source code
of Vue,
just to help us understand the idea of ​​Vue

order

  1. Mount el on the Vue instance
  2. The render function generates virtual DOM
  3. Virtual DOM and data are combined and rendered on the page
  4. Once the data is changed, a new virtual DOM is generated immediately, and the new virtual DOM is compared with the old virtual DOM
  5. Update page

Create your own Vue and execute the mount method in this function

 function myVue( options ) {
    
    
   this._data = options.data;
   // vue 是字符串, 这里是 DOM 
   this._template = document.querySelector( options.el ); 

   this.mount(); // 挂载
 } 

The one with caching function is this render

  • This render function generates a virtual DOM inside
  • Because it needs to have a cache, so use the wording of creator
  • The mount here actually publishes the subscriber mode, passing a watcher
  • In order to facilitate understanding, write here
myVue.prototype.mount = function () {
    
    
  // 需要提供一个 render 方法: 生成 虚拟 DOM
  this.render = this.createRenderFn()

  this.mountComponent();
}

Updata is to render the virtual DOM onto the page

myVue.prototype.mountComponent = function () {
    
    
  // 执行 mountComponent() 函数 
  let mount = () => {
    
    
    this.update( this.render() )  // 渲染虚拟DOM
  }
    mount.call( this ); // 本质应该交给 watcher 来调用
}  

Here is the render function

  • The purpose is to cache the abstract syntax tree (we use virtual DOM to simulate)
  • In the Vue source code, here is the cached abstract syntax tree
myVue.prototype.createRenderFn = function () {
    
    
  let ast = getVNode( this._template );
  // Vue: 将 AST + data => VNode
  // 我们: 带有插值语法的 VNode + data => 含有数据的 VNode
  return function render () {
    
    
    // 将 带有 插值语法的 VNode 转换为 待数据的 VNode
    let _tmp = combine( ast, this._data );
    return _tmp;
  }
}

Render the virtual DOM into the page: the diff algorithm is there


myVue.prototype.update = function () {
    
    
  // 简化, 直接生成 HTML DOM replaceChild 到页面中

}

The design structure of secondary submission is used in the real Vue

  1. There is a one-to-one correspondence between the DOM and the virtual DOM in the page
  2. First AST and data generation VNode (new, render)
  3. Compare the old VNode with the new VNode (diff ), update (update)

The reason why you don’t directly replace it with a new VNode

  • As long as the data changes, a new VNode will be generated, which is a VNode with new data
  • Compare the VNode with the new data with the old VNode. This comparison is the diff algorithm
  • The old VNode and page tags correspond to each other
  • If you replace it directly, the binding relationship between them will have to be re-bound. Re-binding involves recursive traversal access of the tree, which consumes performance.
  • Ignore things that are the same, and update them if they are different. It is equivalent to updating the page

Insert picture description here

  • The functions corresponding to the three functions written above are also displayed
  • createRenderFn (cache abstract syntax tree)
  • render (generate virtual DOM)
  • updata (update)

Constructor of virtual DOM

 class VNode {
    
    
   constructor( tag, data, value, type ) {
    
    
     this.tag = tag && tag.toLowerCase();
     this.data = data;
     this.value = value;
     this.type = type;
     this.children = [];
   }

   appendChild ( vnode ) {
    
    
     this.children.push( vnode );
   }
 }

Generate VNode from HTML DOM: treat this function as a compiler function

 function getVNode( node ) {
    
    
   let nodeType = node.nodeType;
   let _vnode = null;
   if ( nodeType === 1 ) {
    
    
     // 元素
     let nodeName = node.nodeName;
     let attrs = node.attributes;
     let _attrObj = {
    
    };
     for ( let i = 0; i < attrs.length; i++ ) {
    
     // attrs[ i ] 属性节点 ( nodeType == 2 )
       _attrObj[ attrs[ i ].nodeName ] = attrs[ i ].nodeValue;
     }
     _vnode = new VNode( nodeName, _attrObj, undefined, nodeType );

     // 考虑 node 的子元素
     let childNodes = node.childNodes;
     for ( let i = 0; i < childNodes.length; i++ ) {
    
    
       _vnode.appendChild( getVNode( childNodes[ i ] ) ); // 递归
     }

   } else if ( nodeType === 3 ) {
    
    

     _vnode = new VNode( undefined, undefined, node.nodeValue, nodeType );
   }

   return _vnode;
 }

Combine Vnode with interpolation syntax with data data to get VNode filled with data: Simulate AST -> VNode, we wrote in the previous article

 let rkuohao = /\{\{(.+?)\}\}/g; // 用来匹配插值语法的正则表达式
 function combine( vnode, data ) {
    
    
   let _type = vnode.type; // 带上下划线,避免混肴
   let _data = vnode.data;
   let _value = vnode.value;
   let _tag = vnode.tag;
   let _children = vnode.children;
   
   let _vnode = null;
   if ( _type === 3 ) {
    
     // 文本节点 
	 // 对文本处理
     _value = _value.replace( rkuohao, function ( _, g ) {
    
    
       return getValueByPath( data, g.trim() );
     } );
	 _vnode = new VNode( _tag, _data, _value, _type )
	} else if ( _type === 1 ) {
    
     // 元素节点
     _vnode = new VNode( _tag, _data, _value, _type );
     _children.forEach( _subvnode => _vnode.appendChild( combine( _subvnode, data ) ) );
   }

   return _vnode;
 }

Complete source code

<script>
    class VNode {
    
    
      constructor( tag, data, value, type ) {
    
    
        this.tag = tag && tag.toLowerCase();
        this.data = data;
        this.value = value;
        this.type = type;
        this.children = [];
      }

      appendChild ( vnode ) {
    
    
        this.children.push( vnode );
      }
    }
    function getVNode( node ) {
    
    
      let nodeType = node.nodeType;
      let _vnode = null;
      if ( nodeType === 1 ) {
    
    
        // 元素
        let nodeName = node.nodeName;
        let attrs = node.attributes;
        let _attrObj = {
    
    };
        for ( let i = 0; i < attrs.length; i++ ) {
    
     
          _attrObj[ attrs[ i ].nodeName ] = attrs[ i ].nodeValue;
        }
        _vnode = new VNode( nodeName, _attrObj, undefined, nodeType );
        let childNodes = node.childNodes;
        for ( let i = 0; i < childNodes.length; i++ ) {
    
    
          _vnode.appendChild( getVNode( childNodes[ i ] ) ); 
        }
			} else if ( nodeType === 3 ) {
    
    
				_vnode = new VNode( undefined, undefined, node.nodeValue, nodeType );
      }
				return _vnode;
    }


   let rkuohao = /\{\{(.+?)\}\}/g;
   function getValueByPath( obj, path ) {
    
    
     let paths = path.split( '.' ); 
     let res = obj;
     let prop;
     while( prop = paths.shift() ) {
    
    
       res = res[ prop ];
     }
     return res;
   }
   function combine( vnode, data ) {
    
    
     let _type = vnode.type;
     let _data = vnode.data;
     let _value = vnode.value;
     let _tag = vnode.tag;
     let _children = vnode.children;


     let _vnode = null;

     if ( _type === 3 ) {
    
     
       _value = _value.replace( rkuohao, function ( _, g ) {
    
    
         return getValueByPath( data, g.trim() );
       } );
			_vnode = new VNode( _tag, _data, _value, _type )

     } else if ( _type === 1 ) {
    
     
       _vnode = new VNode( _tag, _data, _value, _type );
       _children.forEach( _subvnode => _vnode.appendChild( combine( _subvnode, data ) ) );
     }

     return _vnode;
   }

	function myVue( options ) {
    
    
     this._data = options.data;
     this._template = document.querySelector( options.el ); 

     this.mount(); 
   }  

   myVue.prototype.mount = function () {
    
    
     this.render = this.createRenderFn()

     this.mountComponent();
   }
   myVue.prototype.mountComponent = function () {
    
    
     let mount = () => {
    
    
       this.update( this.render() )
     }
     mount.call( this );
   }
   myVue.prototype.createRenderFn = function () {
    
    
     let ast = getVNode( this._template );
     return function render () {
    
    
       let _tmp = combine( ast, this._data );
       return _tmp;
     }
   }
	myVue.prototype.update = function () {
    
    
     // 简化, 直接生成 HTML DOM replaceChild 到页面中

   }


   let app = new myVue( {
    
    
     el: '#root',
     data: {
    
    
       name: '张三', age: 19
     }
   } );
	app.name = '李四'; // 这个赋值已完成, 页面数据就更新
   
 </script>

Guess you like

Origin blog.csdn.net/m0_47883103/article/details/108650582