render.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /* @flow */
  2. import config from '../config'
  3. import VNode, { emptyVNode } from '../vdom/vnode'
  4. import { normalizeChildren } from '../vdom/helpers'
  5. import {
  6. warn, bind, isObject, toObject,
  7. nextTick, resolveAsset, _toString, toNumber
  8. } from '../util/index'
  9. import { createElement } from '../vdom/create-element'
  10. export const renderState: {
  11. activeInstance: ?Component
  12. } = {
  13. activeInstance: null
  14. }
  15. export function initRender (vm: Component) {
  16. vm._vnode = null
  17. vm._staticTrees = null
  18. vm.$slots = {}
  19. // bind the public createElement fn to this instance
  20. // so that we get proper render context inside it.
  21. vm.$createElement = bind(createElement, vm)
  22. if (vm.$options.el) {
  23. vm.$mount(vm.$options.el)
  24. }
  25. }
  26. export function renderMixin (Vue: Class<Component>) {
  27. Vue.prototype.$nextTick = function (fn: Function) {
  28. nextTick(fn, this)
  29. }
  30. Vue.prototype._render = function (): VNode {
  31. const vm: Component = this
  32. // set current active instance
  33. const prev = renderState.activeInstance
  34. renderState.activeInstance = vm
  35. const {
  36. render,
  37. staticRenderFns,
  38. _renderChildren,
  39. _parentVnode
  40. } = vm.$options
  41. if (staticRenderFns && !this._staticTrees) {
  42. this._staticTrees = []
  43. }
  44. // resolve slots. becaues slots are rendered in parent scope,
  45. // we set the activeInstance to parent.
  46. if (_renderChildren) {
  47. resolveSlots(vm, _renderChildren)
  48. }
  49. // render self
  50. let vnode = render.call(vm._renderProxy, vm.$createElement)
  51. // return empty vnode in case the render function errored out
  52. if (!(vnode instanceof VNode)) {
  53. if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
  54. warn(
  55. 'Multiple root nodes returned from render function. Render function ' +
  56. 'should return a single root node.',
  57. vm
  58. )
  59. }
  60. vnode = emptyVNode()
  61. }
  62. // set parent
  63. vnode.parent = _parentVnode
  64. // restore render state
  65. renderState.activeInstance = prev
  66. return vnode
  67. }
  68. // shorthands used in render functions
  69. Vue.prototype._h = createElement
  70. // toString for mustaches
  71. Vue.prototype._s = _toString
  72. // number conversion
  73. Vue.prototype._n = toNumber
  74. //
  75. Vue.prototype._m = function renderStatic (index?: number): Object | void {
  76. return this._staticTrees[index] || (
  77. this._staticTrees[index] = this.$options.staticRenderFns[index].call(
  78. this._renderProxy
  79. )
  80. )
  81. }
  82. // filter resolution helper
  83. const identity = _ => _
  84. Vue.prototype._f = function resolveFilter (id) {
  85. return resolveAsset(this.$options, 'filters', id, true) || identity
  86. }
  87. // render v-for
  88. Vue.prototype._l = function renderList (
  89. val: any,
  90. render: () => VNode
  91. ): ?Array<VNode> {
  92. let ret: ?Array<VNode>, i, l, keys, key
  93. if (Array.isArray(val)) {
  94. ret = new Array(val.length)
  95. for (i = 0, l = val.length; i < l; i++) {
  96. ret[i] = render(val[i], i)
  97. }
  98. } else if (typeof val === 'number') {
  99. ret = new Array(val)
  100. for (i = 0; i < val; i++) {
  101. ret[i] = render(i + 1, i)
  102. }
  103. } else if (isObject(val)) {
  104. keys = Object.keys(val)
  105. ret = new Array(keys.length)
  106. for (i = 0, l = keys.length; i < l; i++) {
  107. key = keys[i]
  108. ret[i] = render(val[key], key, i)
  109. }
  110. }
  111. return ret
  112. }
  113. // apply v-bind object
  114. Vue.prototype._b = function bindProps (vnode: VNodeWithData, value: any) {
  115. if (value) {
  116. if (!isObject(value)) {
  117. process.env.NODE_ENV !== 'production' && warn(
  118. 'v-bind without argument expects an Object or Array value',
  119. this
  120. )
  121. } else {
  122. if (Array.isArray(value)) {
  123. value = toObject(value)
  124. }
  125. const data = vnode.data
  126. for (const key in value) {
  127. const hash = config.mustUseProp(key)
  128. ? data.props || (data.props = {})
  129. : data.attrs || (data.attrs = {})
  130. hash[key] = value[key]
  131. }
  132. }
  133. }
  134. }
  135. // expose v-on keyCodes
  136. Vue.prototype._k = function getKeyCodes (key: string): any {
  137. return config.keyCodes[key]
  138. }
  139. }
  140. function resolveSlots (
  141. vm: Component,
  142. renderChildren: Array<any> | () => Array<any> | string
  143. ) {
  144. if (renderChildren) {
  145. const children = normalizeChildren(renderChildren) || []
  146. const slots = {}
  147. const defaultSlot = []
  148. let name, child
  149. for (let i = 0, l = children.length; i < l; i++) {
  150. child = children[i]
  151. if ((name = child.data && child.data.slot)) {
  152. const slot = (slots[name] || (slots[name] = []))
  153. if (child.tag === 'template') {
  154. slot.push.apply(slot, child.children)
  155. } else {
  156. slot.push(child)
  157. }
  158. } else {
  159. defaultSlot.push(child)
  160. }
  161. }
  162. if (defaultSlot.length && !(
  163. defaultSlot.length === 1 &&
  164. defaultSlot[0].text === ' '
  165. )) {
  166. slots['default'] = defaultSlot
  167. }
  168. vm.$slots = slots
  169. }
  170. }