create-component.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /* @flow */
  2. import Vue from '../instance/index'
  3. import VNode from './vnode'
  4. import { normalizeChildren } from './helpers'
  5. import { callHook } from '../instance/lifecycle'
  6. import { warn, isObject, hasOwn, hyphenate, validateProp } from '../util/index'
  7. const hooks = { init, prepatch, insert, destroy }
  8. const hooksToMerge = Object.keys(hooks)
  9. export function createComponent (
  10. Ctor: Class<Component> | Function | Object | void,
  11. data?: VNodeData,
  12. parent: Component,
  13. context: Component,
  14. host: ?Component,
  15. children?: VNodeChildren,
  16. tag?: string
  17. ): VNode | void {
  18. // ensure children is a thunk
  19. if (process.env.NODE_ENV !== 'production' &&
  20. children && typeof children !== 'function') {
  21. warn(
  22. 'A component\'s children should be a function that returns the ' +
  23. 'children array. This allows the component to track the children ' +
  24. 'dependencies and optimizes re-rendering.'
  25. )
  26. }
  27. if (!Ctor) {
  28. return
  29. }
  30. if (isObject(Ctor)) {
  31. Ctor = Vue.extend(Ctor)
  32. }
  33. if (typeof Ctor !== 'function') {
  34. if (process.env.NODE_ENV !== 'production') {
  35. warn(`Invalid Component definition: ${Ctor}`, parent)
  36. }
  37. return
  38. }
  39. // async component
  40. if (!Ctor.cid) {
  41. if (Ctor.resolved) {
  42. Ctor = Ctor.resolved
  43. } else {
  44. Ctor = resolveAsyncComponent(Ctor, () => {
  45. // it's ok to queue this on every render because
  46. // $forceUpdate is buffered. this is only called
  47. // if the
  48. parent.$forceUpdate()
  49. })
  50. if (!Ctor) {
  51. // return nothing if this is indeed an async component
  52. // wait for the callback to trigger parent update.
  53. return
  54. }
  55. }
  56. }
  57. data = data || {}
  58. // extract props
  59. const propsData = extractProps(data, Ctor)
  60. // functional component
  61. if (Ctor.options.functional) {
  62. const props = {}
  63. const propOptions = Ctor.options.props
  64. if (propOptions) {
  65. Object.keys(propOptions).forEach(key => {
  66. props[key] = validateProp(key, propOptions, propsData)
  67. })
  68. }
  69. return Ctor.options.render.call(
  70. null,
  71. parent.$createElement,
  72. { props, parent, data, children: () => normalizeChildren(children) }
  73. )
  74. }
  75. // merge component management hooks onto the placeholder node
  76. mergeHooks(data)
  77. // extract listeners, since these needs to be treated as
  78. // child component listeners instead of DOM listeners
  79. const listeners = data.on
  80. if (listeners) {
  81. delete data.on
  82. }
  83. // return a placeholder vnode
  84. const name = Ctor.options.name || tag
  85. const vnode = new VNode(
  86. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  87. data, undefined, undefined, undefined, undefined, context, host,
  88. { Ctor, propsData, listeners, parent, tag, children }
  89. )
  90. return vnode
  91. }
  92. export function createComponentInstanceForVnode (
  93. vnode: any // we know it's MountedComponentVNode but flow doesn't
  94. ): Component {
  95. const vnodeComponentOptions = vnode.componentOptions
  96. const options: InternalComponentOptions = {
  97. _isComponent: true,
  98. parent: vnodeComponentOptions.parent,
  99. propsData: vnodeComponentOptions.propsData,
  100. _componentTag: vnodeComponentOptions.tag,
  101. _parentVnode: vnode,
  102. _parentListeners: vnodeComponentOptions.listeners,
  103. _renderChildren: vnodeComponentOptions.children
  104. }
  105. // check inline-template render functions
  106. const inlineTemplate = vnode.data.inlineTemplate
  107. if (inlineTemplate) {
  108. options.render = inlineTemplate.render
  109. options.staticRenderFns = inlineTemplate.staticRenderFns
  110. }
  111. return new vnodeComponentOptions.Ctor(options)
  112. }
  113. function init (vnode: VNodeWithData, hydrating: boolean) {
  114. if (!vnode.child) {
  115. const child = vnode.child = createComponentInstanceForVnode(vnode)
  116. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  117. }
  118. }
  119. function prepatch (
  120. oldVnode: MountedComponentVNode,
  121. vnode: MountedComponentVNode
  122. ) {
  123. const options = vnode.componentOptions
  124. const child = vnode.child = oldVnode.child
  125. child._updateFromParent(
  126. options.propsData, // updated props
  127. options.listeners, // updated listeners
  128. vnode, // new parent vnode
  129. options.children // new children
  130. )
  131. // always update abstract components.
  132. if (child.$options.abstract) {
  133. child.$forceUpdate()
  134. }
  135. }
  136. function insert (vnode: MountedComponentVNode) {
  137. if (!vnode.child._isMounted) {
  138. vnode.child._isMounted = true
  139. callHook(vnode.child, 'mounted')
  140. }
  141. if (vnode.data.keepAlive) {
  142. vnode.child._inactive = false
  143. callHook(vnode.child, 'activated')
  144. }
  145. }
  146. function destroy (vnode: MountedComponentVNode) {
  147. if (!vnode.child._isDestroyed) {
  148. if (!vnode.data.keepAlive) {
  149. vnode.child.$destroy()
  150. } else {
  151. vnode.child._inactive = true
  152. callHook(vnode.child, 'deactivated')
  153. }
  154. }
  155. }
  156. function resolveAsyncComponent (
  157. factory: Function,
  158. cb: Function
  159. ): Class<Component> | void {
  160. if (factory.requested) {
  161. // pool callbacks
  162. factory.pendingCallbacks.push(cb)
  163. } else {
  164. factory.requested = true
  165. const cbs = factory.pendingCallbacks = [cb]
  166. let sync = true
  167. factory(
  168. // resolve
  169. (res: Object | Class<Component>) => {
  170. if (isObject(res)) {
  171. res = Vue.extend(res)
  172. }
  173. // cache resolved
  174. factory.resolved = res
  175. // invoke callbacks only if this is not a synchronous resolve
  176. // (async resolves are shimmed as synchronous during SSR)
  177. if (!sync) {
  178. for (let i = 0, l = cbs.length; i < l; i++) {
  179. cbs[i](res)
  180. }
  181. }
  182. },
  183. // reject
  184. reason => {
  185. process.env.NODE_ENV !== 'production' && warn(
  186. `Failed to resolve async component: ${factory}` +
  187. (reason ? `\nReason: ${reason}` : '')
  188. )
  189. }
  190. )
  191. sync = false
  192. // return in case resolved synchronously
  193. return factory.resolved
  194. }
  195. }
  196. function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
  197. // we are only extrating raw values here.
  198. // validation and default values are handled in the child
  199. // component itself.
  200. const propOptions = Ctor.options.props
  201. if (!propOptions) {
  202. return
  203. }
  204. const res = {}
  205. const attrs = data.attrs
  206. const props = data.props
  207. const staticAttrs = data.staticAttrs
  208. if (!attrs && !props && !staticAttrs) {
  209. return res
  210. }
  211. for (const key in propOptions) {
  212. const altKey = hyphenate(key)
  213. checkProp(res, attrs, key, altKey) ||
  214. checkProp(res, props, key, altKey) ||
  215. checkProp(res, staticAttrs, key, altKey)
  216. }
  217. return res
  218. }
  219. function checkProp (
  220. res: Object,
  221. hash: ?Object,
  222. key: string,
  223. altKey: string
  224. ): boolean {
  225. if (hash) {
  226. if (hasOwn(hash, key)) {
  227. res[key] = hash[key]
  228. delete hash[key]
  229. return true
  230. } else if (hasOwn(hash, altKey)) {
  231. res[key] = hash[altKey]
  232. delete hash[altKey]
  233. return true
  234. }
  235. }
  236. return false
  237. }
  238. function mergeHooks (data: VNodeData) {
  239. if (!data.hook) {
  240. data.hook = {}
  241. }
  242. for (let i = 0; i < hooksToMerge.length; i++) {
  243. const key = hooksToMerge[i]
  244. const fromParent = data.hook[key]
  245. const ours = hooks[key]
  246. data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours
  247. }
  248. }
  249. function mergeHook (a: Function, b: Function): Function {
  250. // since all hooks have at most two args, use fixed args
  251. // to avoid having to use fn.apply().
  252. return (_, __) => {
  253. a(_, __)
  254. b(_, __)
  255. }
  256. }