render.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import { extend, resolveAsset, hasOwn, isArray, isObject } from '../util/index'
  2. import { createElement, patch, updateListeners, flatten } from '../vdom/index'
  3. import { callHook } from './lifecycle'
  4. import { getPropValue } from './state'
  5. export const renderState = {
  6. activeInstance: null,
  7. context: null
  8. }
  9. export function initRender (vm) {
  10. vm._vnode = null
  11. vm._mounted = false
  12. vm.$slots = {}
  13. const el = vm.$options.el
  14. if (el) {
  15. vm.$mount(el)
  16. }
  17. }
  18. export function renderMixin (Vue) {
  19. // shorthands used in render functions
  20. Vue.prototype.__h__ = createElement
  21. Vue.prototype.__d__ = function (id) {
  22. return resolveAsset(this.$options, 'directives', id, true)
  23. }
  24. Vue.prototype._update = function (vnode) {
  25. if (this._mounted) {
  26. callHook(this, 'beforeUpdate')
  27. }
  28. if (!this._vnode) {
  29. this.$el = patch(this.$el, vnode)
  30. } else {
  31. this.$el = patch(this._vnode, vnode)
  32. }
  33. this._vnode = vnode
  34. if (this._mounted) {
  35. callHook(this, 'updated')
  36. }
  37. }
  38. Vue.prototype._updateFromParent = function (parentData, children, key) {
  39. const oldParentData = this.$options._renderData
  40. this.$options._renderKey = key
  41. this.$options._renderData = parentData
  42. this.$options._renderChildren = children
  43. // update props and listeners
  44. if (parentData) {
  45. // if any prop has changed it would trigger and queue an update,
  46. // but if no props changed, nothing happens
  47. updateProps(this, parentData)
  48. updateEvents(this, parentData, oldParentData)
  49. }
  50. // diff parent data (attrs on the placeholder) and queue update
  51. // if anything changed
  52. if (parentDataChanged(parentData, oldParentData)) {
  53. this.$forceUpdate()
  54. }
  55. }
  56. /**
  57. * Call a render function with this instance as the context.
  58. * This is used to wrap all children thunks in codegen.
  59. */
  60. Vue.prototype._renderWithContext = function (fn) {
  61. return () => {
  62. const prev = renderState.context
  63. renderState.context = this
  64. const children = flatten(fn())
  65. renderState.context = prev
  66. return children
  67. }
  68. }
  69. Vue.prototype._render = function () {
  70. const prev = renderState.activeInstance
  71. renderState.activeInstance = this
  72. const { render, _renderKey, _renderData, _renderChildren } = this.$options
  73. // resolve slots. becaues slots are rendered in parent scope,
  74. // we set the activeInstance to parent.
  75. if (_renderChildren) {
  76. resolveSlots(this, _renderChildren)
  77. }
  78. // render self
  79. const vnode = render.call(this)
  80. // set key
  81. vnode.key = _renderKey
  82. // update parent data
  83. if (_renderData) {
  84. mergeParentData(this, vnode.data, _renderData)
  85. }
  86. // restore render state
  87. renderState.activeInstance = prev
  88. return vnode
  89. }
  90. Vue.prototype.$forceUpdate = function () {
  91. this._watcher.update()
  92. }
  93. }
  94. function resolveSlots (vm, children) {
  95. if (children) {
  96. children = children().slice()
  97. const slots = { default: children }
  98. let i = children.length
  99. let name, child
  100. while (i--) {
  101. child = children[i]
  102. if ((name = child.data && child.data.slot)) {
  103. let slot = (slots[name] || (slots[name] = []))
  104. if (child.tag === 'template') {
  105. slot.push.apply(slot, child.children)
  106. } else {
  107. slot.push(child)
  108. }
  109. children.splice(i, 1)
  110. }
  111. }
  112. vm.$slots = slots
  113. }
  114. }
  115. function parentDataChanged (data, oldData) {
  116. const keys = Object.keys(oldData)
  117. let key, old, cur, i, l, j, k
  118. for (i = 0, l = keys.length; i < l; i++) {
  119. key = keys[i]
  120. cur = data[key]
  121. old = oldData[key]
  122. if (key === 'on') continue
  123. if (!cur) return true
  124. if (isArray(old)) {
  125. if (!isArray(cur)) return true
  126. if (cur.length !== old.length) return true
  127. for (j = 0, k = old.length; j < k; j++) {
  128. if (isObject(old[i])) {
  129. if (!isObject(cur[i])) return true
  130. if (diffObject(cur, old)) return true
  131. } else if (old[i] !== cur[i]) {
  132. return true
  133. }
  134. }
  135. } else if (diffObject(cur, old)) {
  136. return true
  137. }
  138. }
  139. return false
  140. }
  141. function diffObject (cur, old) {
  142. const keys = Object.keys(old)
  143. let i, l, key
  144. for (i = 0, l = keys.length; i < l; i++) {
  145. key = keys[i]
  146. if (cur[key] !== old[key]) return true
  147. }
  148. }
  149. function mergeParentData (vm, data, parentData) {
  150. const props = vm.$options.props
  151. if (parentData.attrs) {
  152. const attrs = data.attrs || (data.attrs = {})
  153. for (let key in parentData.attrs) {
  154. if (!hasOwn(props, key)) {
  155. attrs[key] = parentData.attrs[key]
  156. }
  157. }
  158. }
  159. if (parentData.props) {
  160. const props = data.props || (data.props = {})
  161. for (let key in parentData.props) {
  162. if (!hasOwn(props, key)) {
  163. props[key] = parentData.props[key]
  164. }
  165. }
  166. }
  167. if (parentData.staticClass) {
  168. data.staticClass = data.staticClass
  169. ? data.staticClass + ' ' + parentData.staticClass
  170. : parentData.staticClass
  171. }
  172. if (parentData.class) {
  173. if (!data.class) {
  174. data.class = parentData.class
  175. } else {
  176. data.class = (isArray(data.class) ? data.class : []).concat(parentData.class)
  177. }
  178. }
  179. if (parentData.style) {
  180. if (!data.style) {
  181. data.style = parentData.style
  182. } else {
  183. extend(data.style, parentData.style)
  184. }
  185. }
  186. if (parentData.directives) {
  187. data.directives = parentData.directives.conact(data.directives || [])
  188. }
  189. }
  190. function updateProps (vm, data) {
  191. if (data.attrs || data.props) {
  192. for (let key in vm.$options.props) {
  193. vm[key] = getPropValue(data, key)
  194. }
  195. }
  196. }
  197. function updateEvents (vm, data, oldData) {
  198. if (data.on) {
  199. updateListeners(data.on, oldData.on || {}, (event, handler) => {
  200. vm.$on(event, handler)
  201. })
  202. }
  203. }