除了加深整体流程的理解外重点理解
vdom文件夹里面的js
- vnode、patch、create-component、create-element、create-functional-component (几个重要文件需要好好理解)--> 其中包含
- class VNode 如何表示各种类型的vnode[各种不同的Virtual DOM](如:componentVNode、普通的VNode等等)
- _createElement ($mount最真实的调用)
- createComponent (创建组件VNode的函数)
- createElm (递归创建真实的DOM) (小心被绕晕哦)
- 你可能还需要理解helpers里面的各种工具方法 如 createAsyncPlaceholder 如何解析异步、高级异步组件
- validateComponentName(id) --> 开发环境下验证component的名字
- 定义组件name = options.name|| id
- 调用Vue.extend方法继承Vue 来注册组件。
- 纪录父级和父级id (Super 、SuperId)
- 定义_Ctor 用来缓存已注册的组件。 有就返回简单点
- 验证组件可用性。定义sub构造函数、原型继承、改变指针、记录cid、合并父级配置、定义父级
- 初始化props、computed
- 继承Vue的静态方法(extend、mixin、use、component、filter、directive、一些options
- 缓存当前构造函数
- 返回sub
- 传入的是一个函数不是一个Object所以Vue.options.components [id] = 传入的Func
- 跳过mergeOptions
- 跳过init (lifecycle、events、render、injections、state、provide)
- $mount --> 跳过编译(compileToFunctions) --> mountComponent() -->
- 定义 updateComponent --> new Watcher() --> watcher.get()
-
pushTarget(this) --> 把当前watcher push进targetStack数组 --> 改变静态变量(Dep.target = _target)
-
watcher.getter() --> _update(_render())
-
_render -> render.call() --> (因为initProxy的时候render = hasHandler)
(function anonymous( ) { with(this){ return _c('div',[_v("\n I am parent!!\n "),_c('child'),_v(" "),_c('app'),_v(" "),_c('async-comp')],1) }})
- _v("\n I am parent!!\n ") --> createTextVNode() --> 创建一个文本VNode
- _c( vm , 'child',undefined,undefined,false)--> _createElement() --> createComponent(Ctor[实例时候的Opts],data,vm,children[undefined],tag:['child']) -->
- 拿到baseCtor (Vue) --> 因为Ctor是一个obj --> Ctor = baseCtor.extend(Ctor); (调用Vue.extend创建一个component实例的构造函数)
- resolveConstructorOptions()--> 解析构造函数选项的依赖关系 --> 赋值listeners、on、slot、--> installComponentHooks() --> 安装组件钩子['init','prepatch','insert'.'destroy']
- new Vnode() --> 生成组件vnode并返回vnode --> 一直返回至 _c
- _v(" ") --> createTextVNode() --> 创建一个文本VNode
- _c('app') -->_createElement()--> createComponent(Ctor[先前生成的组件的构造函数],data,vm,children[undefined],tag:['app'])
- (因为本身就是一个构造函数所以不需要extend的逻辑) --> 此构造函数在一开始 Vue.component('app')时候就生成了
- 其他步骤基本同上跳过
- _c('async-comp') -->_createElement()--> createComponent(Ctor[传入的func],data,vm,children[undefined],tag:['async-comp'])
- 来到了isUndef(ctor.id)的逻辑 --> resolveAsyncComponent(手动传入的fn,Vue , vm)
- 组装一些数据【contexts、forceRender、resolve、reject】函数
- res = factory(resolve,reject) 调用传入的函数、并传入组装的对象
- factory无loadling和resolved返回undefined
- 创建一个异步的占位符节点并且返回vnode --> 一直返回到最上级 _c
- 来到了isUndef(ctor.id)的逻辑 --> resolveAsyncComponent(手动传入的fn,Vue , vm)
- --> _createElement(vm,tag:['div'],data:[undefined],children:[之前生产的vnode数组],normalizationType:[1]) --> **
- 最后一个参数为1调用normalizeChildren --> 递归铺平children为一级数组
- 该tag为div是浏览器自带的标签 --> 创建了一个标签vnode
- 然后又一直返回 到最上级调用 --> _update
- 解析参数、拿到oldElm、parentElm --> 调用createElm(vnode:[传入的vnode],inserteVnodeQueue:[],parentElm:[body],nextElm:[textDOM])
- 第一个是一个普通的div-->直接跳到createChildren(vnode,children:【之前生成的而且铺平的vnode数组】,insertedVnodeQueue:[])
- checkDuplicateKeys --> 检查和复制key
- 循环children调用createElm -->
- 第一个是text直接调用createTextNode创建text节点 --> 并插入到父级中(前面创建的div)
- 第二个是child的组件
- 来到createComponent --> 检查是否已经init的组件而且是有keepAlive
- 调用组件init钩子 --> createComponentInstanceForVnode() --> 组装参数 --> 调用new vnode.componentOptions.Ctor(options) (组件构造函数、也是走_init方法) -->initInternalComponent()
- 拿到实例下的构造器下的options 调用create并赋值到vm.$options
- 赋值parent、_parentVnode、propsData、_parentListeners:[child的组件vnode] 、_renderChildren 、 _compentTag:['child']
- 同样的初始化操作 --> 一直往上跳调到 组件的init方法继续走
- child = vnode.componentInstance = createComponentInstanceForVnode的返回值
- child.$mount (主动mount挂载)挂载{<div><p>child component</p></div>}
- 挂载渲染和之前的类似 - -> 最大的差别在于无parent不会insert直接返回渲染的静态DOM 赋值给vm.$el
- 还原activeInstance(在这里你不能忽略他)
- 设置 vm.$el.__vue__ = vm
- 函数调用完生成好了DOM一直往上返回来到组件init
- initComponent -->
- vnode.elm = vnode.componentInstance.$el(赋值DOM)
- isPatchable-->递归查找顶级实例是否存在 --> 存在 -->invokeCreateHooks()
- invokeCreateHooks --> 循环调用收集的cbs.create的函数【update[attrs/class/DOMListener/DOMProps/Style/Direvtives]、_enter/create】
- 拿到组件的钩子函数 create存在调用
- insert存在 调用 往里面insertedVnodeQueue push 当前vnode
- 设置css作用域
- 调用insert方法向父级div插入DOM(一开始生成的最大的DIV)
- 后面的app组件和文本雷同
- 值得注意的是(insertedVnodeQueue)这个变量一直会贯穿整个path过程用于收集插入的组件vnode
- 来到最后一个vnode 异步组件(来源:createComponent --> resolveAsyncComponent && createAsyncPlaceholder --> 拿到的一个注释占位vnode)
- 所以这个createElm创建和插入了一个注释节点
- **如果data存在调用 invokeCreateHooks 就执行一遍cbs.create如果该vnode是一个组件vnode就把该vnode push 进 insertedVnodeQueue **
- insert插入上面生成的DOM树
- 跳出createElm -->移除旧的DOM --> 循环遍历调用组件insert钩子 --> 跳出patch --> 来到_update
- 赋值最后渲染的DOM、和__vue__ 等一系列赋值
- 整个new Vue 结束 挂载了第一次的DOM
- 从setTimeout出发 --> 调用了回调的第一个参数的方法(resolve)、而且传入了一个{template} --> once内部调用 --> ensureCtor (comp , Vue) -->
- comp是一个对象调用Vue.extend(comp) 生成组件 --> 返回一个普通组件的构造函数
- 拿到构造函数赋值给 factory.resolved
- 因为该异步组件已经被解析过了所以 sync为false --> 调用 forceRender(true)
- 循环context(new Vue的实例)--> 调用 $forceUpdate() 执行强制更新
- --> vm._watcher.update() --> queueWatcher(this:[watcher]) -->
- 把当前watcher(以为异步的父级)push到queue(队列中)--> 并把flushSchedulerQueue函数放在下一个microTimerFunc任务执行(nextTick-->异步执行任务)
- flushSchedulerQueue --> (先简单分析一下)
- (queue)对队列中的watcher进行排序 (保证父组件在前子组件在后)
- 循环queue。 调用watcher.before() --> (对于组件而言就是调用new Watcher时候传入的 beforeUdate钩子)
- --> 调用watcher.run() --> this.get() -->
- pushTarget(this) --> value = this.getter.call(vm, vm); --> _render() --> _update()
- 重新 创建vnode 和之前的类同的逻辑
- 注意不同的是这时候 async-comp 已经在Vue中注册(已经是一个可以实例DOM的构造函数)
- render编译所有VNode后调用_update进行真实的DOM渲染,此次就能把异步组件渲染出来
- update() --> patch() --> patchVnode() 进行更新(在后面的更新过程中补充)