component.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import Vue from '../instance/index'
  2. import { callHook } from '../instance/lifecycle'
  3. import { warn, isObject } from '../util/index'
  4. export default function Component (Ctor, data, parent, children) {
  5. if (process.env.NODE_ENV !== 'production' &&
  6. children && typeof children !== 'function') {
  7. warn(
  8. 'A component\'s children should be a function that returns the ' +
  9. 'children array. This allows the component to track the children ' +
  10. 'dependencies and optimizes re-rendering.'
  11. )
  12. }
  13. if (!Ctor) {
  14. return
  15. }
  16. if (isObject(Ctor)) {
  17. Ctor = Vue.extend(Ctor)
  18. }
  19. if (process.env.NODE_ENV !== 'production' && typeof Ctor !== 'function') {
  20. warn(`Invalid Component definition: ${Ctor}`, parent)
  21. return
  22. }
  23. // async component
  24. if (!Ctor.options) {
  25. if (Ctor.resolved) {
  26. Ctor = Ctor.resolved
  27. } else {
  28. resolveAsyncComponent(Ctor, () => parent.$forceUpdate())
  29. return
  30. }
  31. }
  32. // merge hooks on the placeholder node itself
  33. const hook = { init, insert, prepatch, destroy }
  34. if (data.hook) {
  35. for (let key in data.hook) {
  36. let existing = hook[key]
  37. let fromParent = data.hook[key]
  38. hook[key] = existing ? mergeHook(existing, fromParent) : fromParent
  39. }
  40. }
  41. // return a placeholder vnode
  42. return {
  43. tag: 'vue-component-' + Ctor.cid,
  44. key: data && data.key,
  45. data: { hook, Ctor, data, parent, children }
  46. }
  47. }
  48. function mergeHook (a, b) {
  49. // since all hooks have at most two args, use fixed args
  50. // to avoid having to use fn.apply().
  51. return (_, __) => {
  52. a(_, __)
  53. b(_, __)
  54. }
  55. }
  56. function init (vnode) {
  57. const data = vnode.data
  58. const child = new data.Ctor({
  59. parent: data.parent,
  60. _renderData: data.data,
  61. _renderChildren: data.children
  62. })
  63. child.$mount()
  64. data.child = child
  65. }
  66. function insert (vnode) {
  67. callHook(vnode.data.child, 'ready')
  68. }
  69. function prepatch (oldVnode, vnode) {
  70. const old = oldVnode.data
  71. const cur = vnode.data
  72. if (cur.Ctor !== old.Ctor) {
  73. // component changed, teardown and create new
  74. // TODO: keep-alive?
  75. old.child.$destroy()
  76. init(vnode)
  77. } else {
  78. cur.child = old.child
  79. // try re-render child. the child may optimize it
  80. // and just does nothing.
  81. old.child._updateFromParent(cur.data, cur.children, vnode.key)
  82. }
  83. }
  84. function destroy (vnode) {
  85. vnode.data.child.$destroy()
  86. }
  87. function resolveAsyncComponent (factory, cb) {
  88. if (factory.resolved) {
  89. // cached
  90. cb(factory.resolved)
  91. } else if (factory.requested) {
  92. // pool callbacks
  93. factory.pendingCallbacks.push(cb)
  94. } else {
  95. factory.requested = true
  96. const cbs = factory.pendingCallbacks = [cb]
  97. factory(function resolve (res) {
  98. if (isObject(res)) {
  99. res = Vue.extend(res)
  100. }
  101. // cache resolved
  102. factory.resolved = res
  103. // invoke callbacks
  104. for (var i = 0, l = cbs.length; i < l; i++) {
  105. cbs[i](res)
  106. }
  107. }, function reject (reason) {
  108. process.env.NODE_ENV !== 'production' && warn(
  109. `Failed to resolve async component: ${factory}` +
  110. (reason ? `\nReason: ${reason}` : '')
  111. )
  112. })
  113. }
  114. }