You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://github.com/Hanks10100/weex-native-directive/tree/master/component
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>'
})
Vue.mixin({
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
new Profile().$mount('#app')
Vue.extend 函数会保存此时的父构造函数的选项。
Vue.extend = function (extendOptions: Object): Function {
...
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
}
但是我们调用 Vue.mixin 函数改变了 Vue (父构造函数)的选项。如果父构造函数的选项改变了,需要进入 if 语句中,重新合并选项。
// check if there are any late-modified/attached options (#4976)
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
父构造函数能改变选项,子类构造函数同样可以改变选项。根据代码注释,可以找到下面例子:
const Test = Vue.extend({
foo: 'Foo'
})
// Inject options later
// vue-loader and vue-hot-reload-api are doing like this
Test.options.computed = { $style: { test: 'abcde' } }
Test.options.beforeCreate = [
() => { console.log('Should be printed') }
]
Test.options.render = function (h) {
return h('div', '$style: ' + this.$style)
}
// Update super constructor's options
Vue.mixin({})
new Vue({
render: h => h(Test)
}).$mount('#app')
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
...
}
在介绍首次渲染的时候没有介绍
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
这段代码和组件相关,这里详解介绍
createComponent
这里的 createComponent 函数和定义在* src/core/vdom/create-component.js* 文件中同名的 createComponent 函数不一样。我们约定:这里的 createComponent 函数我们称为 A 函数,另外一个称为 B 函数。
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
因为在 B 函数中我们安装了组件的钩子函数,其中就有 init 函数:
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
}
Vue 源码分析(组件渲染)
在首次渲染的时候,我们分析的是 html 标签名的渲染,没有涉及组件的渲染,现在介绍组件渲染。
渲染过程都要调用 _createElement 函数,如果 tag 是组件的话会调用 createComponent 函数。
createComponent
createComponent 定义在 src/core/vdom/create-component.js 文件
createComponent 函数主要有三个大的步骤:构造子类构造函数、安装组件钩子函数、实例化 VNode。
构造子类构造函数
context 是 Vue 实例 vm,baseCtor 是 Vue 构造函数。
isObject(Ctor) 是针对 App.vue 形式的对象,把 App.vue 对象转变为子类构造函数。
这个是处理异步组件,在介绍组件的时候已经分析了
resolveConstructorOptions 处理构造函数选项,定义在 src/core/instance/init.js 文件中
只有子类构造函数才能进入 if 语句。
superOptions 保存父构造函数的选项 options,cachedSuperOptions 保存调用 Vue.extend 函数时的父构造函数的选项 options,然后比较是否相等。这个是为了我们调用 Vue.extend 函数之后,改变了父构造函数的选项之后,好做一个判断。比如说:
Vue.extend 函数会保存此时的父构造函数的选项。
但是我们调用 Vue.mixin 函数改变了 Vue (父构造函数)的选项。如果父构造函数的选项改变了,需要进入 if 语句中,重新合并选项。
父构造函数能改变选项,子类构造函数同样可以改变选项。根据代码注释,可以找到下面例子:
在调用 Vue.extend 函数之后,直接在 Test.options 添加属性。所以通过
查找变动的选项 modifiedOptions,最后合并到 Ctor.extendOptions 上面
安装组件钩子函数
installComponentHooks 函数定义在 src/core/vdom/create-component.js 文件中
installComponentHooks 函数的作用是将 componentVNodeHooks 的钩子函数合并到 data.hooks 。这些钩子函数会在 patch 函数中调用。有一下钩子函数:
实例化 VNode
实例化 VNode 就是调用 new VNode() 实现的,需要注意的是组件实例化,children 参数是 undefined。
最终会调用 patch 函数
patch
首次渲染遇见组件,会进入 if(isUndef(oldVnode)) 语句,调用 createElm(vnode, insertedVnodeQueue)。至于
这段代码在虚拟 dom 的 patch 时会介绍。
createElm
createElm 函数定义在 src/platforms/web/runtime/patch.js 文件
在介绍首次渲染的时候没有介绍
这段代码和组件相关,这里详解介绍
createComponent
这里的 createComponent 函数和定义在* src/core/vdom/create-component.js* 文件中同名的 createComponent 函数不一样。我们约定:这里的 createComponent 函数我们称为 A 函数,另外一个称为 B 函数。
这里的 createComponent 函数定义在 src/platforms/web/runtime/patch.js 文件
因为在 B 函数中我们安装了组件的钩子函数,其中就有 init 函数:
init 钩子函数会调用 createComponentInstanceForVnode 创建一个 Vue 实例,然后调用 $mount 方法挂载子组件。
createComponentInstanceForVnode
最后一句
创建 Vue 子类构造函数的实例。vnode.componentOptions 在实例化 VNode 时候赋值。创建这个实例的时候和首次渲染的流程差不多,不同之处就是走的组件的逻辑,需要注意,在此不多介绍。
继续 A 函数,接下来会执行
initComponent 函数初始化组件,insert 函数将组件插入父元素中。
The text was updated successfully, but these errors were encountered: