vue3.0核心源码

160 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

1。 vonde到真是的DOM是如何转变的

graph TD
创建vnode --> 渲染vnode --> 生成DOM

整个组件渲染流程

image.png

vnode本质上是用来描述DOM的JavaScript对象,它在Vue.js中可以描述不同类型的节点,如普通元素节点、组件节点等

image.png image.png

image.png

vue.js 2.x 和vue.3初始化应用程序

// vue2.x初始化应用程序
import Vue from 'vue'
import App from './App'
const app = new Vue({
    render:h=>h(App)
})
app.$mount('#app')

//vue3.0初始化应用程序
import {createApp} from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')

createApp内部实现

  • 整个app对象创建过程中,vue.js利用闭包合函数柯里化的技巧,很好的实现了参数保留
  • 例如,在执行app.mount的时候,不需要传入渲染器render, 因为在执行createAppAPI的时候炫人气render参数已经被保留下来了
const createApp=((...args)=>{
    //创建app对象
    const app = ensureRenderer().createApp(...args)
    const {mount} = app
    //重新mount方法,目的: 既能让用户在使用API时更加灵活,也兼容了vue.js 2.x的写法,比如app.mount的第一个参数就同时支持选择器字符串和DOM对象2种类型
    app.mount = (containerOrSelector)=>{
        //标准化容器
        const container = normalizeContainer(containerOrSelector)
        if(!container)
            return
        const component = app._component
        //如组件对象没有定义render函数和template模板,则取容器的innerHTML作为组件模板的内容
        if(!isFunction(component) && !component.render && !component.template){
            component.template = container.innerHTML
        }
        //挂载前清空容器内容
        container.innerHTML = ''
        //真正的挂载
        return mount(container)
    }
    return app
})

//ensureRenderer()用来创建一个渲染器对象===============
//渲染相关的一些配置,如更新属性、操作DOM的方法
const rendererOptions={
    patchProp,
    ...nodeOps
}
let renderer
//延时创建渲染器,当用户只依赖响应式包的时候,可以通过tree-shaking移除核心渲染逻辑相关代码
function ensureRenderer(){
    return renderer || (renderer = createRenderer(rendererOptions))
}

function createRenderer(options){
    return baseCreateRenderer(options)
}

function baseCreateRenderer(options){
    function render(vnode,container){
        //组件渲染的核心逻辑
    }
    return{
        render,
        createApp:createAppAPI(render)
    }
}

function createAppAPI(render){
    //createApp接收2个参数:根组件的对象和props
    return function createApp(rootComponent,rootProps=null){
        const app = {
            _component:rootComponent,
            _props:rootProps,
            // 标准的课跨平台的组件渲染流程
            mount(rootContainer){
                //创建根组件的vnode
                const vnode = createVNode(rootComponent,rootProps)
                //利用渲染器渲染vnode
                render(vnode,rootContainer)
                app._container= rootContainer
                return vnode.component.proxy
            }
        }
        return app
    }
}

function createVNode(type,props=null,children=null){
    if(props){
        //处理props相关逻辑,标准化class和style
    }
    //对vnode类型信息编码
    const shapeFlag = isString(type) 
        ? 1 /* ELEMENT*/
        :isSuspense(type)
            ? 128 /*SUSPENSE*/
            :isTeleport(type)
            ? 64 /*TELEPORT*/
            :isObject(type)
                ? 4/*STATEFUL_COMPONENT*/
                :isFunction(type)
                    ?2 /*FUNCTION_COMPONENT*/
                    :0
      const vnode = {
          type,
          props,
          shapeFlag,
          //其它一些属性
      }
      
      //标准化子节点,把不同数据类型的children转成数组或者文本类型
      normalizeChildren(vnode,children){
          return vnode
      }      
}

vnode的优势

  • 抽象: 引入vnode,可以把渲染过程抽象化,从而使组件的抽象能力得到提升
  • 跨平台:因为patch vnode的过程不同平台可以有自己的实现,基于vnode再做服务器渲染、weex平台、小程序平台的渲染

节点挂载

insert执行是在处理子节点后,所以挂载的顺序是先子节点,后父节点,最终挂载到最外层的容器上

function insert(child,parent,anchor){
    if(anchor){
        parent.insertBefore(child,anchor)
    }else{
        parent.appendChild(child)
    }
}