Vue的执行过程

289 阅读3分钟

这是我参与11月更文挑战的第4天

createApp() 创建一个应用app

返回一个对象

{
    component: function(){},
    config: {},
    directive: function(){},
    mixin : function(){},
    mount: function(){},
    provide: function(){},
    unmount: function(){},
    use:function(){},
    version: 3.2.22,
    _component : {
                    data: function(){},
                    methods: {}
                 },
    _container : null,
    _context : {},
    _instance: null,
    _props: null,
    _uid: 0
}

normalizeContainer 格式化挂载容器

app._component.template 初始化组件模板

app._component.template = container.innerHTML

mount(rootContainer) 根组件挂载到根容器中

createVnode(rootComponent) 创建根组件的虚拟dom

function _createVnode(app._component){
    // 0
    // 1: 字符串 element
    // 2: 函数 functional_component
    // 4: 对象 stateful_component
    // 64: teleport
    // 128: suspense
    const shapeFlag = 4
    const vnode = {
        el: null,
        key: null,
        component: null,
        children: null,
        props: null,
        ref: null,
        ...
        patchFlag: 0,
        shapeFlag: 4,
        type: {
            date: {},
            methods: {
                        clickFun: function(){}
                    },
           template: "\n <div @click=\"clickFun\">{{ name }}</div>\n <div>1111</div>\n "
        },
        appContext = app._context
    }
    
}

render(vnode, rootContainer) 将虚拟dom渲染根容器

根组件的虚拟dom渲染到根容器中的过程

patch(app._container._vnode || null, vnode, container){
    // 判断根组件虚拟dom的 vnode.shapeFlag
    // 根组件是一个组件 component
    // 处理组件
    // 每一个createApp()返回的应用都是一个组件,则前面的过程都是一样的
    // 从这里开始是核心的处理过程
    processComponent(n1,n2, container)
}

处理组件 mountComponent

如果是更新,则执行updateComponent() 如果是第一次,则执行mountComponent

function mountComponent(vnode, container){
    // 创建组件实例
    let instance = {
        type: {methods: {…}, template'\n <div @click="clickFun">{{ name }}</div>\n <div>1111</div>\n 'data: ƒ}
        setupComponent(instance)
    }
}

安装组件setupComponent

function setupComponent(){
    const {props, children } = instance.vnode
    // 初始化props
    initProps(instance, props)
    initSlots(instance, children)
    setupStatefulComponent(instance)
}

安装有状态的组件 setupStatefulComponent

function setupStatefulComponent(){
    const Component = instance.type;
    Component =  {methods: {…}, template'\n <div @click="clickFun">{{ name }}</div>\n <div>1111</div>\n 'data: ƒ}
    if(Component.name){}
    if(Component.components){} // 处理子组件
    if(Component.directives){} // 处理指令
    instance.accessCache = object.create(null)
    // 代理组件实例的一些数据
    instance.proxy = markRaw(new Proxy(instance.ctx))
    // 如果组件有setup函数,则执行
    const { setup } = Component
    if(setup){
    }
    finishComponentSetup(instance)
}

完成组件的安装finishComponentSetup

function finishComponentSetup(instance){
    const Component = instance.type
    if(!instance.render){
        const template = Component.template
        if(template){
            // 将模板字符串编译为render()函数
            Component.render = compile(template)
        }
    }
}

核心第一步:模板字符串编译为render()函数

缓存编译结果,有缓存直接使用缓存

const compileCache = object.create(null)
function compileToFunction(template, options){
    // 如果编译过,则直接从缓存中取出来
    // 即只有在第一次渲染页面时才编译
    const key = template
    const cached = compileCache[key]
    if(cached){
        return cached
    }
}

编译第一步:编译生成ast

// template = '\n <div @click="clickFun">{{ name }}</div>\n <div>1111</div>\n '
function baseParse(template){

}
const ast = baseParse(template)
ast = {
    loc: {
            start: 
            end:
            source: 
        },
     cached: 0,
     children: [],
     codegenNode: undefined,
     components: [],
     directives:[],
     helpers: [],
     hoists: [],
     imports: [],
     temps: 0,
     type: 0
    
}

编译第二步 :优化/翻译ast

transform(ast){
}
//  此时ast有一些变化
ast.codegenNode = {
    
    
}
// 变量提升
ast.hoists = [
{
    constType: 0,
    content: "[\"onclick\"]",
    isStatic: false,
    loc: 
    type: 4
},
{

}

]

编译第三步:生成render函数,generate

经过上面编译三步后生成的render函数如下

是通过new Function()将方法字符串生成函数

const render = (new Function(code)() );
compileCache[key] = render
<div id="app">
        <div  @click="clickFun">{{ name }}</div>
        <div>1111</div>
    </div>
(function anonymous(
) {
const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue

const _hoisted_1 = ["onClick"]
const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, "1111", -1 /* HOISTED */)

return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    return (_openBlock(), _createElementBlock(_Fragment, null, [
      _createElementVNode("div", { onClick: clickFun }, _toDisplayString(name), 9 /* TEXT, PROPS */, _hoisted_1),
      _hoisted_2
    ], 64 /* STABLE_FRAGMENT */))
  }
}
})

setupRenderEffect 创建一个render副作用(effect),并传入一个函数

Vue通过一个副作用(effect)来跟踪当前正在运行的函数。

副作用是一个函数的包裹器, 在函数被调用之前就启动跟踪。

Vue知道那个副作用在何时运行,并能在需要时再次执行它。

setupRenderEffect(instance){
    
    // 回调函数
    const componentUpdateFn = () => {
         
    }
    
    // 创建一个render
    const effect = new ReactiveEffect(componentUpdateFn)
    const update = (instance.update = effect.run.bind(effect))
    update()
}

ReactiveEffect

class ReactiveEffect {
    constructor(fn, scheduler, scope){
        this.fn = fn
        this.scheduler = scheduler
        this.active = true
        this.deps = []
    }
    
    run(){
        this.fn()
    }
}

执行render副作用包裹的回调函数

function componentUpdateFn(){
    // 执行render函数,获取虚拟dom
    render.call()
    // 开始patch
}

核心第二步:执行render函数, 生成虚拟dom

执行render函数时,读取到被拦截的变量,会触发该变量绑定的get()方法,get方法会调用副作用的track()方法,进行依赖收集

核心第三步: 对比新旧虚拟dom, 进行diff计算,将计算结果批量异步更新至真实dom

挂载子元素 mountChildren

patch新旧子元素

判断子元素的类型