render.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import Watcher from '../observer/watcher'
  2. import { extend, query, resolveAsset, hasOwn, isArray, isObject } from '../util/index'
  3. import { createElement, patch, updateListeners } from '../vdom/index'
  4. import { callHook } from './lifecycle'
  5. import { getPropValue } from './state'
  6. export const renderState = {
  7. activeInstance: 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._tryUpdate = 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. updateProps(this, parentData)
  46. updateEvents(this, parentData)
  47. }
  48. // for now, if the component has content it always updates
  49. // because we don't know whether the children have changed.
  50. // need to optimize in the future.
  51. if (children || diffParentData(parentData, oldParentData)) {
  52. this.$forceUpdate()
  53. }
  54. }
  55. Vue.prototype._render = function () {
  56. const {
  57. render,
  58. _renderKey,
  59. _renderData,
  60. _renderChildren
  61. } = this.$options
  62. // resolve slots
  63. if (_renderChildren) {
  64. resolveSlots(this, _renderChildren)
  65. }
  66. // render
  67. const prev = renderState.activeInstance
  68. renderState.activeInstance = this
  69. const vnode = render.call(this)
  70. renderState.activeInstance = prev
  71. // set key
  72. vnode.key = _renderKey
  73. // update parent data
  74. if (_renderData) {
  75. mergeParentData(this, vnode.data, _renderData)
  76. }
  77. return vnode
  78. }
  79. Vue.prototype.$mount = function (el) {
  80. callHook(this, 'beforeMount')
  81. this.$el = el && query(el)
  82. if (this.$el) {
  83. this.$el.innerHTML = ''
  84. }
  85. this._watcher = new Watcher(this, this._render, this._update)
  86. this._update(this._watcher.value)
  87. callHook(this, 'mounted')
  88. this._mounted = true
  89. return this
  90. }
  91. Vue.prototype.$forceUpdate = function () {
  92. this._watcher.update()
  93. }
  94. }
  95. function resolveSlots (vm, children) {
  96. if (children) {
  97. children = children.slice()
  98. const slots = { default: children }
  99. let i = children.length
  100. let name, child
  101. while (i--) {
  102. child = children[i]
  103. if ((name = child.data && child.data.slot)) {
  104. let slot = (slots[name] || (slots[name] = []))
  105. if (child.tag === 'template') {
  106. slot.push.apply(slot, child.children)
  107. } else {
  108. slot.push(child)
  109. }
  110. children.splice(i, 1)
  111. }
  112. }
  113. vm.$slots = slots
  114. }
  115. }
  116. function diffParentData (data, oldData) {
  117. let key, old, cur
  118. for (key in oldData) {
  119. cur = data[key]
  120. old = oldData[key]
  121. if (key === 'on') continue
  122. if (!cur) return true
  123. if (isArray(old)) {
  124. if (!isArray(cur)) return true
  125. if (cur.length !== old.length) return true
  126. for (let i = 0; i < old.length; i++) {
  127. if (isObject(old[i])) {
  128. if (!isObject(cur[i])) return true
  129. if (diffObject(cur, old)) return true
  130. } else if (old[i] !== cur[i]) {
  131. return true
  132. }
  133. }
  134. } else if (diffObject(cur, old)) {
  135. return true
  136. }
  137. }
  138. }
  139. function diffObject (cur, old) {
  140. for (var key in old) {
  141. if (cur[key] !== old[key]) return true
  142. }
  143. }
  144. function mergeParentData (vm, data, parentData) {
  145. const props = vm.$options.props
  146. if (parentData.attrs) {
  147. const attrs = data.attrs || (data.attrs = {})
  148. for (let key in parentData.attrs) {
  149. if (!hasOwn(props, key)) {
  150. attrs[key] = parentData.attrs[key]
  151. }
  152. }
  153. }
  154. if (parentData.props) {
  155. const props = data.props || (data.props = {})
  156. for (let key in parentData.props) {
  157. if (!hasOwn(props, key)) {
  158. props[key] = parentData.props[key]
  159. }
  160. }
  161. }
  162. if (parentData.staticClass) {
  163. data.staticClass = data.staticClass
  164. ? data.staticClass + ' ' + parentData.staticClass
  165. : parentData.staticClass
  166. }
  167. if (parentData.class) {
  168. if (!data.class) {
  169. data.class = parentData.class
  170. } else {
  171. data.class = (isArray(data.class) ? data.class : []).concat(parentData.class)
  172. }
  173. }
  174. if (parentData.style) {
  175. if (!data.style) {
  176. data.style = parentData.style
  177. } else {
  178. extend(data.style, parentData.style)
  179. }
  180. }
  181. if (parentData.directives) {
  182. data.directives = parentData.directives.conact(data.directives || [])
  183. }
  184. }
  185. function updateProps (vm, data) {
  186. if (data.attrs || data.props) {
  187. for (let key in vm.$options.props) {
  188. vm[key] = getPropValue(data, key)
  189. }
  190. }
  191. }
  192. function updateEvents (vm, data) {
  193. if (data.on) {
  194. updateListeners(data.on, vm._vnode.data.on || {}, (event, handler) => {
  195. vm.$on(event, handler)
  196. })
  197. }
  198. }