文件路径:node_modules\vue\src\platforms\web\runtime\components\transition.js

    1. /* @flow */
    2. // Provides transition support for a single element/component.
    3. // supports transition mode (out-in / in-out)
    4. import { warn } from 'core/util/index'
    5. import { camelize, extend, isPrimitive } from 'shared/util'
    6. import {
    7. mergeVNodeHook,
    8. isAsyncPlaceholder,
    9. getFirstComponentChild
    10. } from 'core/vdom/helpers/index'
    11. export const transitionProps = {
    12. name: String,
    13. appear: Boolean,
    14. css: Boolean,
    15. mode: String,
    16. type: String,
    17. enterClass: String,
    18. leaveClass: String,
    19. enterToClass: String,
    20. leaveToClass: String,
    21. enterActiveClass: String,
    22. leaveActiveClass: String,
    23. appearClass: String,
    24. appearActiveClass: String,
    25. appearToClass: String,
    26. duration: [Number, String, Object]
    27. }
    28. // in case the child is also an abstract component, e.g. <keep-alive>
    29. // we want to recursively retrieve the real component to be rendered
    30. function getRealChild (vnode: ?VNode): ?VNode {
    31. const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    32. if (compOptions && compOptions.Ctor.options.abstract) {
    33. return getRealChild(getFirstComponentChild(compOptions.children))
    34. } else {
    35. return vnode
    36. }
    37. }
    38. export function extractTransitionData (comp: Component): Object {
    39. const data = {}
    40. const options: ComponentOptions = comp.$options
    41. // props
    42. for (const key in options.propsData) {
    43. data[key] = comp[key]
    44. }
    45. // events.
    46. // extract listeners and pass them directly to the transition methods
    47. const listeners: ?Object = options._parentListeners
    48. for (const key in listeners) {
    49. data[camelize(key)] = listeners[key]
    50. }
    51. return data
    52. }
    53. function placeholder (h: Function, rawChild: VNode): ?VNode {
    54. if (/\d-keep-alive$/.test(rawChild.tag)) {
    55. return h('keep-alive', {
    56. props: rawChild.componentOptions.propsData
    57. })
    58. }
    59. }
    60. function hasParentTransition (vnode: VNode): ?boolean {
    61. while ((vnode = vnode.parent)) {
    62. if (vnode.data.transition) {
    63. return true
    64. }
    65. }
    66. }
    67. function isSameChild (child: VNode, oldChild: VNode): boolean {
    68. return oldChild.key === child.key && oldChild.tag === child.tag
    69. }
    70. const isNotTextNode = (c: VNode) => c.tag || isAsyncPlaceholder(c)
    71. const isVShowDirective = d => d.name === 'show'
    72. export default {
    73. name: 'transition',
    74. props: transitionProps,
    75. abstract: true,
    76. render (h: Function) {
    77. let children: any = this.$slots.default
    78. if (!children) {
    79. return
    80. }
    81. // filter out text nodes (possible whitespaces)
    82. children = children.filter(isNotTextNode)
    83. /* istanbul ignore if */
    84. if (!children.length) {
    85. return
    86. }
    87. // warn multiple elements
    88. if (process.env.NODE_ENV !== 'production' && children.length > 1) {
    89. warn(
    90. '<transition> can only be used on a single element. Use ' +
    91. '<transition-group> for lists.',
    92. this.$parent
    93. )
    94. }
    95. const mode: string = this.mode
    96. // warn invalid mode
    97. if (process.env.NODE_ENV !== 'production' &&
    98. mode && mode !== 'in-out' && mode !== 'out-in'
    99. ) {
    100. warn(
    101. 'invalid <transition> mode: ' + mode,
    102. this.$parent
    103. )
    104. }
    105. const rawChild: VNode = children[0]
    106. // if this is a component root node and the component's
    107. // parent container node also has transition, skip.
    108. if (hasParentTransition(this.$vnode)) {
    109. return rawChild
    110. }
    111. // apply transition data to child
    112. // use getRealChild() to ignore abstract components e.g. keep-alive
    113. const child: ?VNode = getRealChild(rawChild)
    114. /* istanbul ignore if */
    115. if (!child) {
    116. return rawChild
    117. }
    118. if (this._leaving) {
    119. return placeholder(h, rawChild)
    120. }
    121. // ensure a key that is unique to the vnode type and to this transition
    122. // component instance. This key will be used to remove pending leaving nodes
    123. // during entering.
    124. const id: string = `__transition-${this._uid}-`
    125. child.key = child.key == null
    126. ? child.isComment
    127. ? id + 'comment'
    128. : id + child.tag
    129. : isPrimitive(child.key)
    130. ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
    131. : child.key
    132. const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)
    133. const oldRawChild: VNode = this._vnode
    134. const oldChild: VNode = getRealChild(oldRawChild)
    135. // mark v-show
    136. // so that the transition module can hand over the control to the directive
    137. if (child.data.directives && child.data.directives.some(isVShowDirective)) {
    138. child.data.show = true
    139. }
    140. if (
    141. oldChild &&
    142. oldChild.data &&
    143. !isSameChild(child, oldChild) &&
    144. !isAsyncPlaceholder(oldChild) &&
    145. // #6687 component root is a comment node
    146. !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)
    147. ) {
    148. // replace old child transition data with fresh one
    149. // important for dynamic transitions!
    150. const oldData: Object = oldChild.data.transition = extend({}, data)
    151. // handle transition mode
    152. if (mode === 'out-in') {
    153. // return placeholder node and queue update when leave finishes
    154. this._leaving = true
    155. mergeVNodeHook(oldData, 'afterLeave', () => {
    156. this._leaving = false
    157. this.$forceUpdate()
    158. })
    159. return placeholder(h, rawChild)
    160. } else if (mode === 'in-out') {
    161. if (isAsyncPlaceholder(child)) {
    162. return oldRawChild
    163. }
    164. let delayedLeave
    165. const performLeave = () => { delayedLeave() }
    166. mergeVNodeHook(data, 'afterEnter', performLeave)
    167. mergeVNodeHook(data, 'enterCancelled', performLeave)
    168. mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave })
    169. }
    170. }
    171. return rawChild
    172. }
    173. }