您现在的位置是:首页 >学无止境 >vue2首次渲染过程网站首页学无止境

vue2首次渲染过程

Neo 丶 2024-09-12 12:01:04
简介vue2首次渲染过程

实例成员初始化

1.首先在 core/instance/index 中生命vue的构造函数后,经过一系列的操作初始化了一些实例成员

// 初始化 _init 方法,也就是初始化整个Vue的入口
initMixin(Vue)

// 初始化 $data 和 $props 实例成员,指向实例的_data和_props,并禁止修改,只能获取
// 初始化实例上的 $set 和 $delete 方法,和静态成功set、delete一样
// 初始化 $watch,用于创建userWatcher并返回取消监听的方法
stateMixin(Vue)

// 初始化$on、$off、$emit、$destory、$forceUpdate, 
eventsMixin(Vue)

// 初始化_update、$destory、$forceUpdate, 
// 其中 $forceUpdate 用于执行当前实例渲染$watcher 的update方法立即更新
lifecycleMixin(Vue)

// 挂载渲染相关辅助函数
// 挂载 $nextTick,和静态成员 nextTick 是一个方法
// 挂载 _render,用于调用用户传入的render方法,创建虚拟DOM
renderMixin(Vue)

静态成员初始化

2.通过initGlobalAPI声明vue构造函数的静态成员 


export function initGlobalAPI(Vue: GlobalAPI) {
  // config
  const configDef: Record<string, any> = {}
  configDef.get = () => config
  // 初始化了Vue.config这个静态属性
  Object.defineProperty(Vue, 'config', configDef)
    
  // 定义了Vue.util,并挂载了一部分API(warn/extend/mergeOptions/defineReactive)
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  
  // 定义三个静态方法(set/delete/nextTick)
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 定义observable方法,内部调用observe,
  // 让入参对象变成一个响应式对象,并返回这个响应式对象
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
  
  // 初始化了Vue.options对象,其__proto__指向null
  Vue.options = Object.create(null)

  // 在其上扩展了三个方法(component/directive/filter)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // 利用Vue.options._base暂存Vue构造函数
  Vue.options._base = Vue
  // 将内建组件下的全部组件都拷贝给Vue.options.components
  // extend实现了两个对象属性的浅拷贝
  extend(Vue.options.components, builtInComponents)
  // 注册vue.use方法,用于插件注册
  initUse(Vue)
  // 注册Vue.mixin方法,用于全局混入
  initMixin(Vue)
  // 注册Vue.extend,基于传入的options返回一个组件的构造函数
  initExtend(Vue)
  // 注册Vue.directive,Vue.component和Vue.filter三个静态方法
  initAssetRegisters(Vue)
}

3.在 web/runtime/index 中初始化了平台相关的内容,主要是实例成员 $mount 方法

4.如果是编译器版本,需要在 src/platform/web/runtime/entry-runtime-with-compiler 中重写$mount方法

执行vue构造函数

5.首次渲染时执行 new Vue(options),执行_init方法

export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    // 使用vm存储vue实力
    const vm: Component = this
    // 设置自增长唯一键uin
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    // 用于测试性能,可了解下window.performance
    if (__DEV__ && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // 后续设置响应式对象时通过该标记来忽略,不进行 observer 操作
    vm._isVue = true
    // avoid instances from being observed
    vm.__v_skip = true
    // effect scope
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      // 组件实例化 合并选项参数
      initInternalComponent(vm, options as any)
    } else {
      // Vue实例化 合并选项参数
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    // 代理 vm._renderProxy 到 自身,主要用来检测 vm 上属性的读取操作
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 添加一些实例变量,设置初始空值
    initLifecycle(vm)
    // 初始化设置 $on $off 自定义事件
    initEvents(vm)
     // 扩展 createElement 函数赋给 vm 为动态方法 $createElement、_c 等
    initRender(vm)
    // 调用钩子函数 beforeCreate
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    // 解析 inject 选项
    initInjections(vm) // resolve injections before data/props
    // props,methods,data,computed,watch 做了一些列初始化操作
    initState(vm)
    // 解析 provide 选项,配合 inject 一起使用
    initProvide(vm) // resolve provide after data/props
    // 调用钩子函数 created
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 挂载渲染页面
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

$mount

6.$mount会调用 mountComponent 方法

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
   // 使用 $el存储el, 并挂载到实例上 vm.$el = el
  vm.$el = el
  if (!vm.$options.render) {
    // ...
  }
  // 调用钩子函数 beforeMount
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (__DEV__ && config.performance && mark) {
    // ...
  } else {
    // 内部调用了_update方法,传入了vm_render()函数作为参数
    // vm._render()函数内渲染了我们之前编译出来的render函数,并生成虚拟dom vnode
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }
  
  const watcherOptions: WatcherOptions = {
    before() {
      // 如果mounted钩子已经执行过了,而当前组件还未销毁
      // 此时,如果产生更新,说明并非首次渲染,那么执行beforeUpdate钩子
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(
    vm,
    updateComponent,
    noop, // 空函数
    watcherOptions, // 触发beforeUpdate
    true /* isRenderWatcher */
  )
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // 若当前vnode为null,即之前没有生成过虚拟dom,即首次渲染
  if (vm.$vnode == null) {
    vm._isMounted = true
    // 调用钩子函数 mounted,首次渲染完成
    callHook(vm, 'mounted')
  }
  return vm
}

7.进入 watcher 构造函数

export default class Watcher implements DepTarget {
  constructor(
    vm: Component | null,
    expOrFn: string | (() => any), // 传入的更新函数
    cb: Function,
    options?: WatcherOptions | null,
    isRenderWatcher?: boolean
  ) {
    // 如果是渲染watcher,直接赋值给vm._watcher
    if ((this.vm = vm) && isRenderWatcher) {
      vm._watcher = this
    }
    // parse expression for getter
    // 将我们传入的函数赋值给getter,即render函数
    if (isFunction(expOrFn)) {
      this.getter = expOrFn
    } else {
      // ...
    }
    // 调用get()
    this.value = this.lazy ? undefined : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get() {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 即上面传入的render函数,并触发虚拟dom更新真实dom
      value = this.getter.call(vm, vm)
    } catch (e: any) {
      // ...
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    // 返回渲染后的真实dom
    return value
  }

  // ...其他方法
}

整个beforemount到mounted过程中,主要做的工作:

1.渲染render函数称为虚拟dom

2.执行vm._update函数,将虚拟dom转化为真实dom

如果是beforeupdate到updated钩子之间,说明不是首次渲染,那么虚拟dom会有新旧两个。此时vm._update函数的作用就是对比新旧两个vnode,得出差异,更新需要更新的地方。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。