Vue——Vue初始化【三】

简介: Vue——Vue初始化【三】

前言

今天我们来解密下init.ts中的代码内容,并结合 vue 生命周期来分析下 vue 的初始化;

GitHub

github page

内容

init.ts

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
import type { Component } from 'types/component'
import type { InternalComponentOptions } from 'types/options'
import { EffectScope } from 'v3/reactivity/effectScope'
// vue 实例id
let uid = 0
export function initMixin(Vue: typeof Component) {
  // 接收实例化传入的参数
  Vue.prototype._init = function (options?: Record<string, any>) {
    //vue 实例
    const vm: Component = this
    // 每个vue实例都有对应的一个实例id
    vm._uid = uid++
    let startTag, endTag
    /* istanbul ignore if */
    // 代码覆盖率测试
    if (__DEV__ && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    // a flag to mark this as a Vue instance without having to do instanceof
    // check
    // 标记作为vue的实例不必去执行instanceof
    vm._isVue = true
    // avoid instances from being observed
    // 避免实例被observed观察
    vm.__v_skip = true
    // effect scope
    // https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md
    // 对内部的响应式对象的副作用effect进行统一管理
    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.
      // 由于动态选项合并非常缓慢,并且没有一个内部组件选项需要特殊处理。
      // 传入vue实例并进行组件初始化
      initInternalComponent(vm, options as any)
    } else {
      // 根组件配置 | 合并参数
      vm.$options = mergeOptions(
        // 解析构造函数参数
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    // 代码覆盖测试
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 核心的核心
    // 初始化生命周期
    initLifecycle(vm)
    // 初始化事件监听
    initEvents(vm)
    // 初始化渲染
    initRender(vm)
    // 调用生命周期的钩子函数 | beforeCreate
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    // https://v2.cn.vuejs.org/v2/api/#provide-inject
    // 在data/props前进行inject
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    // https://v2.cn.vuejs.org/v2/api/#provide-inject
    // provide 父组件提供的数据
    // inject 子组件进行注入后直接使用
    // 在data/props后进行provide
    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)
    }
    // 组件如果设置了el则挂载到指定的el上
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
/**
 * 初始化内部组件
 *
 * @param vm
 * @param options
 */
export function initInternalComponent(
  vm: Component,
  options: InternalComponentOptions
) {
  const opts = (vm.$options = Object.create((vm.constructor as any).options))
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode
  const vnodeComponentOptions = parentVnode.componentOptions!
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag
  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}
/**
 * 解析构造函数的选项
 *
 * @param Ctor
 * @returns
 */
export function resolveConstructorOptions(Ctor: typeof Component) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}
/**
 * 解析修改的选项
 *
 * @param Ctor
 * @returns
 */
function resolveModifiedOptions(
  Ctor: typeof Component
): Record<string, any> | null {
  let modified
  const latest = Ctor.options
  const sealed = Ctor.sealedOptions
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = latest[key]
    }
  }
  return modified
}

Demo 演示

demo 位于example/docs/01.lifecycle.html

通过debugger的方式,能够更直观的查看到整个调用的过程;这里罗列了选项式 api 和组合式 api,后续的 demo 都会以组合式 api 为主。

具体的 debugger 方法可以查看微软的文档devtools-guide-chromium,一般来说 F9 进行调试即可;如果你想跳过某一函数,那就 F10;

<script src="../../dist/vue.js"></script>
<div id="app">{{msg}}</div>
<script>
  debugger
  //   Options API || 设置了el
  //   var app = new Vue({
  //     el: '#app',
  //     data: {
  //       msg: 'Hello Vue!'
  //     },
  //     beforeCreate() {
  //       console.log('beforeCreate')
  //     },
  //     created() {
  //       console.log('created')
  //     },
  //     beforeMount() {
  //       console.log('beforeMount')
  //     },
  //     mounted() {
  //       console.log('mounted')
  //     }
  //   })
  // Options API || 手动$mount
  //   new Vue({
  //     data: () => ({
  //       msg: 'helloWord'
  //     }),
  //     beforeCreate: () => {
  //       console.log('beforeCreate')
  //     },
  //     created: () => {
  //       console.log('created')
  //     },
  //     beforeMount: () => {
  //       console.log('beforeMount')
  //     },
  //     mounted: () => {
  //       console.log('mounted')
  //     }
  //   }).$mount('#app')
  // Composition API
  const { ref, beforeCreate, created, beforeMount, mounted } = Vue
  new Vue({
    setup(props) {
      const msg = ref('Hello World!')
      return { msg }
    },
    beforeCreate() {
      console.log('beforeCreate')
    },
    created() {
      console.log('created')
    },
    beforeMount() {
      console.log('beforeMount')
    },
    mounted() {
      console.log('mounted')
    }
  }).$mount('#app')
</script>

内容总结

这里我们总结下init.ts中大致的内容

  1. 生成 vue 实例 Id;
  2. 标记 vue 实例;
  3. 如果是子组件则传入 vue 实例和选项并初始化组件,否则则进行选项参数合并,将用户传入的选项和构造函数本身的选项进行合并;
  4. 初始化实例生命周期相关属性,如:parent������、root、children�ℎ������、refs 等;
  5. 初始化组件相关的事件监听,父级存在监听事件则挂载到当前实例上;
  6. 初始化渲染,如:slots�����、scopedSlots、createElement�������������、attrs、$listeners;
  7. 调用beforeCreate生命周期钩子函数
  8. 初始化注入数据,在 data/props 之前进行 inject,以允许一个祖先组件向其所有子孙后代注入一个依赖(说白了就是有个传家宝,爷爷要想传给孙子,那就要爸爸先 inject,再给儿子)
  9. 初始化状态,如:props、setup、methods、data(|| observe)、computed、watch
  10. 在 data/props 之后进行 provide
  11. 调用created生命周期钩子函数,完成初始化
  12. 如果设置了el则自动挂载到对应的元素上,不然就要自己$mount

学无止境,谦卑而行.

目录
相关文章
|
1月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
226 2
|
8天前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
214 137
|
4月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
655 0
|
4月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
3月前
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
160 0
|
3月前
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
316 1
|
4月前
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
414 17
|
4月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
238 1
|
4月前
|
存储 JavaScript 前端开发
如何高效实现 vue 文件批量下载及相关操作技巧
在Vue项目中,实现文件批量下载是常见需求。例如文档管理系统或图片库应用中,用户可能需要一次性下载多个文件。本文介绍了三种技术方案:1) 使用`file-saver`和`jszip`插件在前端打包文件为ZIP并下载;2) 借助后端接口完成文件压缩与传输;3) 使用`StreamSaver`解决大文件下载问题。同时,通过在线教育平台的实例详细说明了前后端的具体实现步骤,帮助开发者根据项目需求选择合适方案。
415 0
|
4月前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
176 0