Vue源码解析——Vue初始化及首次渲染过程

源码调试地址

https://github.com/KingComedy/vue-source-debugger

Vue的初始化

在引入的Vue的时候,定义了Vue的构造函数,以及初始化了Vue的实例方法和静态方法,总结了有以下一些比较重要的方法:

初始化实例方法,主要在src\core\instance\index.js文件里

  • _init方法(Vue实例初始化的方法)
  • 初始化set、set、delete、$watch方法(用于监听或触发响应式更新的方法)
  • 初始化on、on、off、once、once、emit(发布订阅的事件机制)
  • 初始化forceUpdate、_update(用于组件更新函数)、forceUpdate、u​pdate(用于组件更新函数)、destroy
  • 初始化$nextTick
  • 初始化_render(用于将组件转为虚拟dom)
  • 初始化Vue.prototype.patch (主要讲虚拟dom转为真实dom并挂载 以及 dom的更新时执行的diff对比)。 src\platforms\web\runtime\patch.js中定义
  • 初始化Vue.prototype.mount方法 (生命周期create执行之后,mounted执行之前执行),在src\platforms\web\runtime\index.js定义,在src\platforms\web\entry-runtime-with-compiler.js扩展了mount方法(生命周期create执行之后,mounted执行之前执行),在src\platforms\web\runtime\index.js定义,在src\platforms\web\entry−runtime−with−compiler.js扩展了mount方法

初始化静态成员,在src\core\index.js文件里

  • 初始化Vue.options 和 Vue.config静态属性
  • 初始化Vue.options._base 为Vue构造函数
  • 初始化Vue.options的component、directive、filter3个属性
  • 初始化Vue.util 的 defineReactive、extend、mergeOptions、warn的四个方法
  • 初始化Vue.set、delete、nextTick、observable 四个静态方法(和set、set、delete、$nextTick是同一个方法)
  • 注册了keep-alive组件
  • 注册了Vue.Use、Vue.mixin、Vue.extend静态方法

Vue的首次渲染

new Vue实例化时,执行_init方法(src\core\instance\init.js)

执行_init方法(在src\core\instance\init.js定义)

主要源码:

// 对传入的配置与默认配置合并
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
  vm._self = vm
  initLifecycle(vm) // 初始化组件的$parent\$root\$children\$ref属性
  initEvents(vm) // 初始化组件的事件监听
  initRender(vm) // 初始化$slots属性, 定义 $createElement和vm._c方法(用于生成虚拟dom),对组件的$listeners、$attrs做响应式处理
  callHook(vm, 'beforeCreate') // 执行beforeCreate生命周期函数
  initInjections(vm) // 获取组件的inject所有属性,并做响应式处理
  initState(vm) // 组件数据的初始化以及响应式处理:按顺序 props => methods => data => computed => watch
  initProvide(vm) // 获取组件的provide所有属性,并做响应式处理
  callHook(vm, 'created') // 执行created生命周期函数
  // 处理完组件相关的数据后,开始执行$mount方法
  if (vm.$options.el) {
    vm.$mount(vm.$options.el) // $mount 在src\platforms\web\entry-runtime-with-compiler.js文件中执行了扩展
  }

 

执行$mount(在src\platforms\web\runtime\index.js定义)

  • mount方法是在src\platforms\web\runtime\index.js定义,如果是提供编译器的vue版本,在src\platforms\web\entry-runtime-with-compiler.js文件中对mount方法是在src\platforms\web\runtime\index.js定义,如果是提供编译器的vue版本,在src\platforms\web\entry−runtime−with−compiler.js文件中对mount方法进行扩展
  • 扩展的主要内容:获取组件的render函数,优先级:options.render => template => el
    • 1 如果vue组件中是否已经有自带的render函数,继续执行$mount
    • 2 如果有template,将template转为render函数
    • 3 如果有el属性,通过el获取dom,并转为template,再将template转为render函数
  • 得到render函数后,赋值给vm.options.render, 继续执行options.render,继续执行mount
  • 最终执行mountComponent函数(在src\core\instance\lifecycle.js定义)

执行mountComponent(在src\core\instance\lifecycle.js定义)

  • 执行beforeMount生命周期
  • 设置组件更新函数updateComponent => vm._update(vm._render(), hydrating )
  • 实例化Watcher,传入组件更新函数updateComponent
  • Watcher实例化时,会触发一次组件更新,即执行updateComponent
    • 先执行vm_render(即$mount里生成的render函数),生成当前组件的虚拟dom 即vnode,并传入vm._update
    • 执行vm._update(在src\core\instance\lifecycle.js定义)
      • _update主要是执行vm.patch(在src\platforms\web\runtime\index.js定义)
      • 最终执行patch(在src\core\vdom\patch.js文件中所返回的patch函数),无论是首次渲染还是组件更新,最后都是执行patch函数
      • 因为是首次渲染,所以主要流程是将传入的虚拟dom转为真实dom,并挂载到真实dom上,主要是深入遍历执行createElm
        • createElm先执行createComponent(在src\core\vdom\patch.js定义),如果遇到的是自定义组件,则不会继续往下执行
          • 如果遇到的是自定义组件,获取组件的init的钩子函数,并执行。(init钩子函数,在vm._render中 执行createComponent时 执行钩子函数的安装)
          • init主要是执行组件的实例化,即执行Vue的实例方法_init,并执行实例$mount方法
          • 即当遇到子组件时,会走子组件的渲染流程,即 _init => $mount => mountComponent => new Watcher => updateComponent => 子组件的mounted生命周期
        • 如果不是自定义组件,创建dom元素,执行createChildren遍历vnode.chidrend,执行createElm,创建子元素,并执行insert添加到父dom元素下
        • createChildren深度遍历创建完所有子dom元素后,执行最后的insert,将根元素插入到页面中,整个首次渲染结束
  • 执行根组件的mounted生命周期函数

vm._render的主要作用:(在src\core\instance\render.js定义)

  • 主要是执行vm.options.render.call(vm._renderProxy, vm.options.render.call(vm.r​enderProxy,vm.createElement)
  • vm._renderProxy即vm实例,vm.$createElement则在initRender(src\core\instance\render.js)中初始化
  • vm.$createElement中执行createElement(src\core\vdom\create-element.js中定义)
  • createElement对传入的参数做进一步的处理,比如有时传入两个参数或者三个参数,最终执行_createElement,开始创建虚拟dom
  • 执行_createElement:通过标签类型创建虚拟dom
    • 判断传入的标签tag是否是字符串类型,如果是字符串,判断是否是原生dom标签,如果是直接new VNode创建虚拟dom
    • 如果不是,则通过tag在vm.$options.components里找到对应的组件的构造函数Ctor,执行createComponent并传入构造函数Ctor
    • 执行createComponent(在src\core\vdom\create-component.js定义)
    • installComponentHooks安装组件各阶段相对应的钩子,默认钩子componentVNodeHooks有init、prepatch、insert、destroy 4个管理钩子
    • 创建虚拟dom

createElm的主要作用:(在src\core\vdom\patch.js定义)

  • 如果遇到的是自定义组件,获取组件的init的钩子函数,并执行。
  • init主要是执行组件的实例化,即执行Vue的实例方法_init,并执行实例$mount方法
  • 即当遇到子组件时,会走子组件的渲染流程,即 _init => $mount => mountComponent => new Watcher => updateComponent => 子组件的mounted生命周期
  • 如果不是自定义组件,创建dom元素,执行createChildren遍历vnode.chidrend,执行createElm,创建子元素,并执行insert添加到父dom元素下
  • createChildren深度遍历创建完所有子dom元素后,执行最后的insert,将根元素插入到页面中,整个首次渲染结束

总结

组件的首次渲染过程

  • _init => mount => mountComponent => new Watcher => updateComponent => vm._render => vm._update => vm.__patch__ => createElm创建根元素 => createChildren(深度遍历执行createElm创建子元素 ) => 如果遇到子组件child,执行child_init => child.mount => ... => 直到子组件下所有的子元素创建完成 => 执行子组件的mounted => 直到所有子元素创建完成 => 执行组件的mounted

父子组件的生命周期顺序

  • beforeCreate 和 created 都是在组件实例的_init里执行,而子组件是在 patch阶段才开始创建
  • 只有所有子元素创建并挂载(如果有子组件,要等子组件执行完mounted), 最终才执行父元素的mounted
  • 所以顺序为:parent created => child created => child mounted => parent mounted

组件并不是拆分越细越好

  • 组件的初始化 要多创建许多属性,和做一些组件所需的一些操作,所以组件抽象需要合理,并不是组件拆的越细越好~
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值