简易实现vue渲染器

90 阅读2分钟

1.Vue中模板被解析成虚拟Dom的形式

下面这段模板经过vue的解析会成为这样的一个虚拟Dom对象

 <template>
        <div onClick="alert('hello')">
            <span>span</span>
            <p>p</p>
        </div>
  </template>
  
  //解析为虚拟Dom
    const myVnode = {
            tag:'div',
            props:{
                onClick:()=>alert('hello'),
                
            },
            children:[{tag:'span',children:'span'},{tag:'p',children:'p'}]
        }

2.根据虚拟Dom的数据结果编写简单的渲染器实现基本功能


function myRender  (vnode,container){
            const el = document.createElement(vnode.tag) //创建真实dom节点
             //遍历props所有方法,如果是以on开头,就是dom事件,为该节点添加事件
            for(const key in vnode.props){
                if(/^on/.test(key)){
                    el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
                }
            }
            
            if(typeof vnode.children==='string'){
            //判断children是否为字符串,如果是,创建文字节点并添加
            //这里为什么不用innerHtml,因为如果字符串为<strong>aa</strong>,会被解析成加粗的节点
                el.appendChild(document.createTextNode(vnode.children))
            }else if(Array.isArray(vnode.children)){
            //如果children是数组,这个数组是当前节点的所有子节点的虚拟Dom,循环递归怎么渲染器方法
                vnode.children.forEach(item=>myRender(item,el))
            }
            //将创建的真实节点加入已经有的真实节点
            container.appendChild(el)
        }
        
        
        

所有代码

  <div id="app"></div>
    
    <script>
        const app = document.querySelector('#app')
        function myRender  (vnode,container){
            const el = document.createElement(vnode.tag)
            for(const key in vnode.props){
                if(/^on/.test(key)){
                    el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
                }
            }

            if(typeof vnode.children==='string'){
                el.appendChild(document.createTextNode(vnode.children))
            }else if(Array.isArray(vnode.children)){
                vnode.children.forEach(item=>myRender(item,el))
            }
            container.appendChild(el)
        }

        const myVnode = {
            tag:'div',
            props:{
                onClick:()=>alert('hello'),
                
            },
            children:[{tag:'span',children:'span'},{tag:'p',children:'p'}]
        }

        myRender(myVnode,app)
    </script>

运行结果

image.png

3.渲染组件功能

只渲染模板肯定的不行的,vue最突出的特色就是组件化开发,怎样才能渲染组件,首先就要理解什么是组件, 用vue官方的话来说,组价就是一组Dom元素的封装,所以我们就可以定义一个函数来代表组件,实质返回的也是虚拟dom对象

//封装的组件
 const MyComponent = function(){
                return {
                    tag:'div',
                    props:{
                        onClick:()=>alert('hello')
                    },
                    children:'component'
                }
            }
            
const vnode={ //虚拟Dom存储组件
              tag:MyComponent
            }

现在为了可以渲染组件,需要改造renderer函数

            function renderer(vnode,container){
                if(typeof vnode.tag==='string'){ //如果描述是普通标签元素,调用mountElement完成渲染
                    mountElement(vnode,container)
                }else if(typeof vnode.tag==="function"){ //是组件的话走渲染组件的方法
                    mountComponent(vnode,container)
                }
            }

mountElement方法和上面渲染普通标签的方法一样

            function mountElement(vnode,container){
                const el = document.createElement(vnode.tag)
                for(const key in vnode.props){
                    if(/^on/.test(key)){
                        el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
                    }
                }
                if(typeof vnode.children==="string"){
                    el.appendChild(document.createTextNode(vnode.children))
                }else if(Array.isArray(vnode.children)){
                    vnode.children.forEach(item=>mountElement(item,el))
                }


                container.appendChild(el)
            }

mountComponent是处理组件的方法

            function mountComponent(vnode,container){

                const subtree = vnode.tag() 
                //执行封装组件的函数获得组件内的标签模板,就是获得组件的虚拟Dom对象
                renderer(subtree,container)
                //执行rendere渲染函数,因为组件里面可能有普通标签,或者还有组件
            }

完整代码

 const MyComponent = function(){
                return {
                    tag:'div',
                    props:{
                        onClick:()=>alert('hello')
                    },
                    children:'component'
                }
            }

            const vnode={
                tag:MyComponent
            }

            function renderer(vnode,container){
                if(typeof vnode.tag==='string'){
                    mountElement(vnode,container)
                }else if(typeof vnode.tag==="function"){
                    mountComponent(vnode,container)
                }
            }

            function mountElement(vnode,container){
                const el = document.createElement(vnode.tag)
                for(const key in vnode.props){
                    if(/^on/.test(key)){
                        el.addEventListener(key.substr(2).toLowerCase(),vnode.props[key])
                    }
                }
                if(typeof vnode.children==="string"){
                    el.appendChild(document.createTextNode(vnode.children))
                }else if(Array.isArray(vnode.children)){
                    vnode.children.forEach(item=>mountElement(item,el))
                }


                container.appendChild(el)
            }

            function mountComponent(vnode,container){

                const subtree = vnode.tag()
                renderer(subtree,container)
            }

            renderer(vnode,app)
   

执行结果

image.png