82cf8fbd1e8d646a67b0303161e48665d385da92335356ade778d48ba3f8df3e882a35e5fef160e27c0c84c944b9d56b1accee45bba863816cfcddc13b4425 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /* @flow */
  2. import VNode, { cloneVNode } from './vnode'
  3. import { createElement } from './create-element'
  4. import { resolveInject } from '../instance/inject'
  5. import { normalizeChildren } from '../vdom/helpers/normalize-children'
  6. import { resolveSlots } from '../instance/render-helpers/resolve-slots'
  7. import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
  8. import { installRenderHelpers } from '../instance/render-helpers/index'
  9. import {
  10. isDef,
  11. isTrue,
  12. hasOwn,
  13. camelize,
  14. emptyObject,
  15. validateProp
  16. } from '../util/index'
  17. export function FunctionalRenderContext (
  18. data: VNodeData,
  19. props: Object,
  20. children: ?Array<VNode>,
  21. parent: Component,
  22. Ctor: Class<Component>
  23. ) {
  24. const options = Ctor.options
  25. // ensure the createElement function in functional components
  26. // gets a unique context - this is necessary for correct named slot check
  27. let contextVm
  28. if (hasOwn(parent, '_uid')) {
  29. contextVm = Object.create(parent)
  30. // $flow-disable-line
  31. contextVm._original = parent
  32. } else {
  33. // the context vm passed in is a functional context as well.
  34. // in this case we want to make sure we are able to get a hold to the
  35. // real context instance.
  36. contextVm = parent
  37. // $flow-disable-line
  38. parent = parent._original
  39. }
  40. const isCompiled = isTrue(options._compiled)
  41. const needNormalization = !isCompiled
  42. this.data = data
  43. this.props = props
  44. this.children = children
  45. this.parent = parent
  46. this.listeners = data.on || emptyObject
  47. this.injections = resolveInject(options.inject, parent)
  48. this.slots = () => {
  49. if (!this.$slots) {
  50. normalizeScopedSlots(
  51. data.scopedSlots,
  52. this.$slots = resolveSlots(children, parent)
  53. )
  54. }
  55. return this.$slots
  56. }
  57. Object.defineProperty(this, 'scopedSlots', ({
  58. enumerable: true,
  59. get () {
  60. return normalizeScopedSlots(data.scopedSlots, this.slots())
  61. }
  62. }: any))
  63. // support for compiled functional template
  64. if (isCompiled) {
  65. // exposing $options for renderStatic()
  66. this.$options = options
  67. // pre-resolve slots for renderSlot()
  68. this.$slots = this.slots()
  69. this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots)
  70. }
  71. if (options._scopeId) {
  72. this._c = (a, b, c, d) => {
  73. const vnode = createElement(contextVm, a, b, c, d, needNormalization)
  74. if (vnode && !Array.isArray(vnode)) {
  75. vnode.fnScopeId = options._scopeId
  76. vnode.fnContext = parent
  77. }
  78. return vnode
  79. }
  80. } else {
  81. this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization)
  82. }
  83. }
  84. installRenderHelpers(FunctionalRenderContext.prototype)
  85. export function createFunctionalComponent (
  86. Ctor: Class<Component>,
  87. propsData: ?Object,
  88. data: VNodeData,
  89. contextVm: Component,
  90. children: ?Array<VNode>
  91. ): VNode | Array<VNode> | void {
  92. const options = Ctor.options
  93. const props = {}
  94. const propOptions = options.props
  95. if (isDef(propOptions)) {
  96. for (const key in propOptions) {
  97. props[key] = validateProp(key, propOptions, propsData || emptyObject)
  98. }
  99. } else {
  100. if (isDef(data.attrs)) mergeProps(props, data.attrs)
  101. if (isDef(data.props)) mergeProps(props, data.props)
  102. }
  103. const renderContext = new FunctionalRenderContext(
  104. data,
  105. props,
  106. children,
  107. contextVm,
  108. Ctor
  109. )
  110. const vnode = options.render.call(null, renderContext._c, renderContext)
  111. if (vnode instanceof VNode) {
  112. return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
  113. } else if (Array.isArray(vnode)) {
  114. const vnodes = normalizeChildren(vnode) || []
  115. const res = new Array(vnodes.length)
  116. for (let i = 0; i < vnodes.length; i++) {
  117. res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)
  118. }
  119. return res
  120. }
  121. }
  122. function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
  123. // #7817 clone node before setting fnContext, otherwise if the node is reused
  124. // (e.g. it was from a cached normal slot) the fnContext causes named slots
  125. // that should not be matched to match.
  126. const clone = cloneVNode(vnode)
  127. clone.fnContext = contextVm
  128. clone.fnOptions = options
  129. if (process.env.NODE_ENV !== 'production') {
  130. (clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext
  131. }
  132. if (data.slot) {
  133. (clone.data || (clone.data = {})).slot = data.slot
  134. }
  135. return clone
  136. }
  137. function mergeProps (to, from) {
  138. for (const key in from) {
  139. to[camelize(key)] = from[key]
  140. }
  141. }