|
1 | 1 | # createComponent的实现过程 |
2 | 2 |
|
| 3 | +```js |
| 4 | +export function createComponent ( |
| 5 | + Ctor: Class<Component> | Function | Object | void, |
| 6 | + data: ?VNodeData, |
| 7 | + context: Component, |
| 8 | + children: ?Array<VNode>, |
| 9 | + tag?: string |
| 10 | +): VNode | Array<VNode> | void { |
| 11 | + if (isUndef(Ctor)) { |
| 12 | + return |
| 13 | + } |
| 14 | + // 这里的context就是Vue的实例,然后这个_base实际就是Vue |
| 15 | + const baseCtor = context.$options._base |
| 16 | + |
| 17 | + // plain options object: turn it into a constructor |
| 18 | + // 这里调用extend方法就是用Vue的一个子类来代表这个组件 |
| 19 | + if (isObject(Ctor)) { |
| 20 | + Ctor = baseCtor.extend(Ctor) |
| 21 | + } |
| 22 | + |
| 23 | + // if at this stage it's not a constructor or an async component factory, |
| 24 | + // reject. |
| 25 | + if (typeof Ctor !== 'function') { |
| 26 | + if (process.env.NODE_ENV !== 'production') { |
| 27 | + warn(`Invalid Component definition: ${String(Ctor)}`, context) |
| 28 | + } |
| 29 | + return |
| 30 | + } |
| 31 | + |
| 32 | + // async component |
| 33 | + let asyncFactory |
| 34 | + if (isUndef(Ctor.cid)) { |
| 35 | + asyncFactory = Ctor |
| 36 | + Ctor = resolveAsyncComponent(asyncFactory, baseCtor) |
| 37 | + if (Ctor === undefined) { |
| 38 | + // return a placeholder node for async component, which is rendered |
| 39 | + // as a comment node but preserves all the raw information for the node. |
| 40 | + // the information will be used for async server-rendering and hydration. |
| 41 | + return createAsyncPlaceholder( |
| 42 | + asyncFactory, |
| 43 | + data, |
| 44 | + context, |
| 45 | + children, |
| 46 | + tag |
| 47 | + ) |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + data = data || {} |
| 52 | + |
| 53 | + // resolve constructor options in case global mixins are applied after |
| 54 | + // component constructor creation |
| 55 | + resolveConstructorOptions(Ctor) |
| 56 | + |
| 57 | + // transform component v-model data into props & events |
| 58 | + if (isDef(data.model)) { |
| 59 | + transformModel(Ctor.options, data) |
| 60 | + } |
| 61 | + |
| 62 | + // extract props |
| 63 | + const propsData = extractPropsFromVNodeData(data, Ctor, tag) |
| 64 | + |
| 65 | + // functional component |
| 66 | + // 如果是函数式组件 |
| 67 | + if (isTrue(Ctor.options.functional)) { |
| 68 | + return createFunctionalComponent(Ctor, propsData, data, context, children) |
| 69 | + } |
| 70 | + |
| 71 | + // extract listeners, since these needs to be treated as |
| 72 | + // child component listeners instead of DOM listeners |
| 73 | + const listeners = data.on |
| 74 | + // replace with listeners with .native modifier |
| 75 | + // so it gets processed during parent component patch. |
| 76 | + data.on = data.nativeOn |
| 77 | + |
| 78 | + if (isTrue(Ctor.options.abstract)) { |
| 79 | + // abstract components do not keep anything |
| 80 | + // other than props & listeners & slot |
| 81 | + |
| 82 | + // work around flow |
| 83 | + const slot = data.slot |
| 84 | + data = {} |
| 85 | + if (slot) { |
| 86 | + data.slot = slot |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + // install component management hooks onto the placeholder node |
| 91 | + // 安装Hook,这个在后边的patch会用到的 |
| 92 | + installComponentHooks(data) |
| 93 | + |
| 94 | + // return a placeholder vnode |
| 95 | + const name = Ctor.options.name || tag |
| 96 | + // 创建VNode |
| 97 | + const vnode = new VNode( |
| 98 | + `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, |
| 99 | + data, undefined, undefined, undefined, context, |
| 100 | + { Ctor, propsData, listeners, tag, children }, |
| 101 | + asyncFactory |
| 102 | + ) |
| 103 | + |
| 104 | + // Weex specific: invoke recycle-list optimized @render function for |
| 105 | + // extracting cell-slot template. |
| 106 | + // https://github.com/Hanks10100/weex-native-directive/tree/master/component |
| 107 | + /* istanbul ignore if */ |
| 108 | + if (__WEEX__ && isRecyclableComponent(vnode)) { |
| 109 | + return renderRecyclableComponentTemplate(vnode) |
| 110 | + } |
| 111 | + |
| 112 | + return vnode |
| 113 | +} |
| 114 | +``` |
| 115 | +
|
| 116 | +
|
| 117 | +
|
| 118 | +```js |
| 119 | +// 当data中merged为true时,就是把默认的一些hook和data中的Hook合并在一块,如果相同,按顺序执行 |
| 120 | +function installComponentHooks (data: VNodeData) { |
| 121 | + const hooks = data.hook || (data.hook = {}) |
| 122 | + for (let i = 0; i < hooksToMerge.length; i++) { |
| 123 | + const key = hooksToMerge[i] |
| 124 | + const existing = hooks[key] |
| 125 | + const toMerge = componentVNodeHooks[key] |
| 126 | + if (existing !== toMerge && !(existing && existing._merged)) { |
| 127 | + hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge |
| 128 | + } |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +function mergeHook (f1: any, f2: any): Function { |
| 133 | + const merged = (a, b) => { |
| 134 | + // flow complains about extra args which is why we use any |
| 135 | + f1(a, b) |
| 136 | + f2(a, b) |
| 137 | + } |
| 138 | + merged._merged = true |
| 139 | + return merged |
| 140 | +} |
| 141 | + |
| 142 | +const hooksToMerge = Object.keys(componentVNodeHooks) |
| 143 | +``` |
| 144 | +
|
| 145 | +
|
| 146 | +
|
| 147 | +```js |
| 148 | +const componentVNodeHooks = { |
| 149 | + init (vnode: VNodeWithData, hydrating: boolean): ?boolean { |
| 150 | + if ( |
| 151 | + vnode.componentInstance && |
| 152 | + !vnode.componentInstance._isDestroyed && |
| 153 | + vnode.data.keepAlive |
| 154 | + ) { |
| 155 | + // kept-alive components, treat as a patch |
| 156 | + const mountedNode: any = vnode // work around flow |
| 157 | + componentVNodeHooks.prepatch(mountedNode, mountedNode) |
| 158 | + } else { |
| 159 | + const child = vnode.componentInstance = createComponentInstanceForVnode( |
| 160 | + vnode, |
| 161 | + activeInstance |
| 162 | + ) |
| 163 | + child.$mount(hydrating ? vnode.elm : undefined, hydrating) |
| 164 | + } |
| 165 | + }, |
| 166 | + |
| 167 | + prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { |
| 168 | + const options = vnode.componentOptions |
| 169 | + const child = vnode.componentInstance = oldVnode.componentInstance |
| 170 | + updateChildComponent( |
| 171 | + child, |
| 172 | + options.propsData, // updated props |
| 173 | + options.listeners, // updated listeners |
| 174 | + vnode, // new parent vnode |
| 175 | + options.children // new children |
| 176 | + ) |
| 177 | + }, |
| 178 | + |
| 179 | + insert (vnode: MountedComponentVNode) { |
| 180 | + const { context, componentInstance } = vnode |
| 181 | + if (!componentInstance._isMounted) { |
| 182 | + componentInstance._isMounted = true |
| 183 | + callHook(componentInstance, 'mounted') |
| 184 | + } |
| 185 | + if (vnode.data.keepAlive) { |
| 186 | + if (context._isMounted) { |
| 187 | + // vue-router#1212 |
| 188 | + // During updates, a kept-alive component's child components may |
| 189 | + // change, so directly walking the tree here may call activated hooks |
| 190 | + // on incorrect children. Instead we push them into a queue which will |
| 191 | + // be processed after the whole patch process ended. |
| 192 | + queueActivatedComponent(componentInstance) |
| 193 | + } else { |
| 194 | + activateChildComponent(componentInstance, true /* direct */) |
| 195 | + } |
| 196 | + } |
| 197 | + }, |
| 198 | + |
| 199 | + destroy (vnode: MountedComponentVNode) { |
| 200 | + const { componentInstance } = vnode |
| 201 | + if (!componentInstance._isDestroyed) { |
| 202 | + if (!vnode.data.keepAlive) { |
| 203 | + componentInstance.$destroy() |
| 204 | + } else { |
| 205 | + deactivateChildComponent(componentInstance, true /* direct */) |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | +} |
| 210 | +``` |
| 211 | +
|
| 212 | +
|
| 213 | +
|
0 commit comments