cuda合并访问的要求_合并配置、组件注册、生命周期和异步组件
合并配置:1、了解外部调用场景(new Vue)的配置合并;2、了解组件场景的配置合并
if (options && options._isComponent) {
//将 options 和 Vue.options 合并到 Sub.options,合并的结果保留在 vm.$options
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
在执行 this._init(options) 会有两种合并方式,回调用这个 init 函数有两种情况,一种是初始化 Vue 时,一种是组件初始化时,第一种情况下,会进入到 else 逻辑,mergeOptions 函数实际上就是将 Vue.options 和 options 作合并,Vue.options 在初始化全局 Vue API 时被定义,实际上就是将三个属性添加给 Vue.options,并且都初始化为一个对象:
Vue.options.components = {}
Vue.options.directives = {}
Vue.options.filters = {}
mergeOptions(parent,child,vm?) 主要是把 parent 和 child 这两个对象根据一些合并策略,合并成一个新对象,并返回这个新对象。在这个过程中会将 extends 和 mixins 扩展或者混合的对象也会合并进来。
接下来看组件初始化合并的情况:
由于组件的构造函数(Sub)是通过 Vue.extend 继承自 Vue 的,在生成组件构造函数的过程中,会将 Vue.options 和 options 进行合并,将结果存储在 Sub.options 中,并且合并 options 的过程会走到 initInternalComponent(vm, options) 的逻辑,这个函数会将 Sub.options 赋值给 vm.$options,因此我们可以通过 vm.$options 访问到 Vue.options 和 自定义 options。
Sub.options = mergeOptions(
Super.options, //Super 为 Vue
extendOptions //自定义的 options
)
组件注册目标:1、了解全局组件和局部组件的注册方式;2、了解两种注册的差异
注册全局组件方法:Vue.component(tagName, options)
在文件入口,会初始化全局 Vue API,Vue.component 就在其中被定义为一个返回该组件构造函数的方法,并且该方法会将该组件构造器储存在 this.options.component.id 属性中,其中 this 代表了 Vue,因此 Vue.options.component.id 可以访问到 Sub.options,而上面讲合并配置时说到,Vue.options 和 自定义 options 合并的结果存放到了 Sub.options,因此注册全局组件的方式是将自定义的 options 扩展到了 Vue.options 中,而在每个生成每个组件构造函数的过程中,都会将 Vue.options 合并到 vm.$options 中,所以最终全局组件可以被任意其他组件使用。
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
//看这段 if 语句
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
//看这句
this.options[type + 's'][id] = definition
return definition
}
}
})
然后在组件实例化阶段,即 new Sub() 的过程中,会进入到组件初始化,继而进入到配置合并的流程,initInternalComponent(vm, options) 会将 Sub.options 存放到 vm.$options 中。
注册局部组件方法,例如:
import HelloWorld from './components/HelloWorld'
export default {
components: {
HelloWorld
}
}
其实就是走 initInternalComponent(vm, options) 这个逻辑,会将 components 合并到 vm.$options.components 上,所以 vm 可以访问到 HelloWorld 这个子组件。但只有注册了这个局部组件的组件才能访问这个局部组件,因为局部组件的 options 并没有扩展到 Vue.options 中。
生命周期目标:1、了解 Vue.js 有哪些生命周期;2、了解各个生命周期的时机以及我们能作的事情;
依然从源码角度来分析:
initLifecycle(vm) //初始化生命周期
initEvents(vm) //初始化事件
initRender(vm)
callHook(vm, 'beforeCreate') //执行钩子
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') //执行钩子
beforeCreate 和 created
当 init 时,会先进行事件和生命周期的初始化,然后执行 beforeCreate 钩子,initState 这个函数会初始化 props、data、methods、watch、computed 等属性,那么显然 beforeCreate 钩子就 不能获取到 props 和 data 中定义的值,也不能调用 methods 中定义的函数,之后执行 created 钩子。
beforeMount 和 mounted
发生在 $mount 过程中,在 vm._render 函数渲染 vnode 之前,执行了 beforeMount 钩子,在将 vnode patch 到真实 DOM 后,执行了 mounted 钩子,但是 new Vue 中的 mounted 钩子和组件 mounted 钩子执行时机是不同的,当增加渲染 wather 后,就会执行 new Vue 的 mounted 钩子,当组件 vnode patch 到真实 DOM 后,会执行 invokeInsertHook 函数,这个函数会将 insertedVnodeQueue 数组(在子组件初始化过程中,会不断地将当前子组件 vnode push 到 insertedVnodeQueue 数组中。)中的每一个组件 vnode 都执行它们对应的 insert hook(组件钩子),这个组件钩子会执行 mounted 钩子。
beforeMount 先父后子
Mounted 先子后父,原因是 push 进 insertedVnodeQueue 数组的组件 vnode 是先子后父
beforeUpdate 和 updated
了解响应式原理之后,再详细写这个部分,简单来说就是当数据发生改变时,会执行这个两个钩子。要求是必须在 mounted 钩子执行完之后。
beforeDestroy 和 destroyed
它们的执行时机是在组件销毁的阶段,了解组件销毁原理之后,再详细写这部分。
beforeDestroy 先父后子
destroyed 先子后父
异步组件有三种实现方式:1、工厂函数;2、promise 对象;3、高级异步组件
异步组件优化首屏加载效率的一种有效方式,可以让加载的资源包变小,同时 JS 执行时间减少,渲染的 DOM 树体积变少。
工厂函数:注册组件时,不再是对象,而是一个工厂函数,工厂函数中使用异步加载组件的方式:
//回调函数来实现异步
Vue.component('async-example', function (resolve, reject) {
// 这个特殊的 require 语法告诉 webpack
// 自动将编译后的代码分割成不同的块,
// 这些块将通过 Ajax 请求自动下载。
require(['./my-async-component'], resolve) //这是一个异步加载过程,只有当结果返回后才会执行 resolve 回调函数
})
工厂函数会被作为参数传入给 createComponent,进入到异步组件处理的逻辑,将工厂函数和 Vue 传给 resolveAsyncComponent 函数,以下是核心代码:
//once 函数通过闭包和标志位使 resolve 和 reject 只会执行一次
const resolve = once((res: Object | Class<Component>) => {
// cache resolved
//这个函数将返回一个异步组件的构造器
factory.resolved = ensureCtor(res, baseCtor)
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if (!sync) {
//最终又会再一次进入到 createComponent,拿到异步组件构造器之后,之后的流程跟同步组件一样
//之所以要强制渲染,是因为无法保证异步组件加载过程中的数据发生变化
//但当异步加载完成后,必须再次渲染,必须再次执行
//第一次渲染 createComponent 生成的是空 vnode最终被渲染成了注释节点,而这一次是要生成异步组件 vnode
forceRender(true)
} else {
owners.length = 0
}
})
异步组件第一个渲染时只会渲染出一个注释节点,作为异步组件加载完成之前的占位符,当异步组件加载成功后,就会执行 resolve 函数,就会进入到 forceRender 进行第二次渲染,会直接获取到异步组件的构造函数,最终生成一个异步组件的 vnode,之后的流程跟同步组件是一样的,最终组件节点会代替注释节点。
Promise 异步组件:配合了 webpack 的 import 语法,返回一个 promise 对象
//promise 来实现异步
Vue.component('async-example', () => import('my-async-component')
//在 vue 源码中,这样定义了:
let res = factory(resolve, reject);
//在异步组件加载得到返回结果后
res.then(resolve, reject)
PS:无论哪种方式,传入的参数 resolve 和 reject 都是源码中定义好的方法,前者在加载到组件对象后,会进行重新渲染,渲染组件,后者也会重新渲染,如果有 error 组件,就会渲染出 error 组件,这个可以用下面的高级组件进行配置。
高级组件:为了处理加载过程中以及加载失败的情况
const AsyncComp = () => ({
// 需要加载的组件。应当是一个 Promise
component: import('./MyComp.vue'),
// 加载中应当渲染的组件
loading: LoadingComp,
// 出错时渲染的组件
error: ErrorComp,
// 渲染加载中组件前的等待时间。默认:200ms。
delay: 200,
// 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
timeout: 3000
})
Vue.component('async-example', AsyncComp)
异步组件第一次执行时,看 delay 是否为 0,如果为 0,则直接渲染 loading 组件,否则就渲染成一个注释节点(异步组件的占位符);
之后再次渲染会有四种状态:
1、reject 状态:如果异步组件加载失败,则渲染 error 组件;
2、resolve 状态:如果异步组件加载成功,则渲染加载成功的组件 vnode;
3、loading 状态:如果异步组件在加载中,则渲染 loading 组件;
4、timeout 状态:如果过了 timeout 的时间还没有加载成功,则渲染 error 组件
有人会有疑问:异步组件会渲染两次(使用高级组件时可能会渲染三次),是不是会不利于页面性能优化?
首先我们使用异步组件是为了按需加载,最主要的功能是优化首屏性能,可以让加载的资源包大小变小,同时 JS 执行时间减少,渲染的的 DOM 树体积也变少,而异步加载组件后的渲染,已经是第二次渲染,非首屏渲染了,影响的不是首屏性能。但是由于异步组件增加了 HTTP 请求,所以当资源包不是特别大的时候(app.js 不大于 1 MB 时),使用异步组件会有相反的作用。