mirror of https://github.com/vuejs/core.git
refactor: use symbol for private properties (#8681)
This commit is contained in:
parent
02c6924bcd
commit
2ffe3d5b3e
|
@ -22,6 +22,9 @@ import { RendererElement } from '../renderer'
|
|||
|
||||
type Hook<T = () => void> = T | T[]
|
||||
|
||||
const leaveCbKey = Symbol('_leaveCb')
|
||||
const enterCbKey = Symbol('_enterCb')
|
||||
|
||||
export interface BaseTransitionProps<HostElement = RendererElement> {
|
||||
mode?: 'in-out' | 'out-in' | 'default'
|
||||
appear?: boolean
|
||||
|
@ -89,8 +92,8 @@ export interface TransitionElement {
|
|||
// in persisted mode (e.g. v-show), the same element is toggled, so the
|
||||
// pending enter/leave callbacks may need to be cancelled if the state is toggled
|
||||
// before it finishes.
|
||||
_enterCb?: PendingCallback
|
||||
_leaveCb?: PendingCallback
|
||||
[enterCbKey]?: PendingCallback
|
||||
[leaveCbKey]?: PendingCallback
|
||||
}
|
||||
|
||||
export function useTransitionState(): TransitionState {
|
||||
|
@ -259,9 +262,9 @@ const BaseTransitionImpl: ComponentOptions = {
|
|||
)
|
||||
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
|
||||
// early removal callback
|
||||
el._leaveCb = () => {
|
||||
el[leaveCbKey] = () => {
|
||||
earlyRemove()
|
||||
el._leaveCb = undefined
|
||||
el[leaveCbKey] = undefined
|
||||
delete enterHooks.delayedLeave
|
||||
}
|
||||
enterHooks.delayedLeave = delayedLeave
|
||||
|
@ -366,18 +369,18 @@ export function resolveTransitionHooks(
|
|||
}
|
||||
}
|
||||
// for same element (v-show)
|
||||
if (el._leaveCb) {
|
||||
el._leaveCb(true /* cancelled */)
|
||||
if (el[leaveCbKey]) {
|
||||
el[leaveCbKey](true /* cancelled */)
|
||||
}
|
||||
// for toggled element with same key (v-if)
|
||||
const leavingVNode = leavingVNodesCache[key]
|
||||
if (
|
||||
leavingVNode &&
|
||||
isSameVNodeType(vnode, leavingVNode) &&
|
||||
leavingVNode.el!._leaveCb
|
||||
(leavingVNode.el as TransitionElement)[leaveCbKey]
|
||||
) {
|
||||
// force early removal (not cancelled)
|
||||
leavingVNode.el!._leaveCb()
|
||||
;(leavingVNode.el as TransitionElement)[leaveCbKey]!()
|
||||
}
|
||||
callHook(hook, [el])
|
||||
},
|
||||
|
@ -396,7 +399,7 @@ export function resolveTransitionHooks(
|
|||
}
|
||||
}
|
||||
let called = false
|
||||
const done = (el._enterCb = (cancelled?) => {
|
||||
const done = (el[enterCbKey] = (cancelled?) => {
|
||||
if (called) return
|
||||
called = true
|
||||
if (cancelled) {
|
||||
|
@ -407,7 +410,7 @@ export function resolveTransitionHooks(
|
|||
if (hooks.delayedLeave) {
|
||||
hooks.delayedLeave()
|
||||
}
|
||||
el._enterCb = undefined
|
||||
el[enterCbKey] = undefined
|
||||
})
|
||||
if (hook) {
|
||||
callAsyncHook(hook, [el, done])
|
||||
|
@ -418,15 +421,15 @@ export function resolveTransitionHooks(
|
|||
|
||||
leave(el, remove) {
|
||||
const key = String(vnode.key)
|
||||
if (el._enterCb) {
|
||||
el._enterCb(true /* cancelled */)
|
||||
if (el[enterCbKey]) {
|
||||
el[enterCbKey](true /* cancelled */)
|
||||
}
|
||||
if (state.isUnmounting) {
|
||||
return remove()
|
||||
}
|
||||
callHook(onBeforeLeave, [el])
|
||||
let called = false
|
||||
const done = (el._leaveCb = (cancelled?) => {
|
||||
const done = (el[leaveCbKey] = (cancelled?) => {
|
||||
if (called) return
|
||||
called = true
|
||||
remove()
|
||||
|
@ -435,7 +438,7 @@ export function resolveTransitionHooks(
|
|||
} else {
|
||||
callHook(onAfterLeave, [el])
|
||||
}
|
||||
el._leaveCb = undefined
|
||||
el[leaveCbKey] = undefined
|
||||
if (leavingVNodesCache[key] === vnode) {
|
||||
delete leavingVNodesCache[key]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { patchProp } from '../src/patchProp'
|
||||
import { ElementWithTransition } from '../src/components/Transition'
|
||||
import { ElementWithTransition, vtcKey } from '../src/components/Transition'
|
||||
import { svgNS } from '../src/nodeOps'
|
||||
|
||||
describe('runtime-dom: class patching', () => {
|
||||
|
@ -13,12 +13,12 @@ describe('runtime-dom: class patching', () => {
|
|||
|
||||
test('transition class', () => {
|
||||
const el = document.createElement('div') as ElementWithTransition
|
||||
el._vtc = new Set(['bar', 'baz'])
|
||||
el[vtcKey] = new Set(['bar', 'baz'])
|
||||
patchProp(el, 'class', null, 'foo')
|
||||
expect(el.className).toBe('foo bar baz')
|
||||
patchProp(el, 'class', null, null)
|
||||
expect(el.className).toBe('bar baz')
|
||||
delete el._vtc
|
||||
delete el[vtcKey]
|
||||
patchProp(el, 'class', null, 'foo')
|
||||
expect(el.className).toBe('foo')
|
||||
})
|
||||
|
|
|
@ -32,12 +32,14 @@ export interface TransitionProps extends BaseTransitionProps<Element> {
|
|||
leaveToClass?: string
|
||||
}
|
||||
|
||||
export const vtcKey = Symbol('_vtc')
|
||||
|
||||
export interface ElementWithTransition extends HTMLElement {
|
||||
// _vtc = Vue Transition Classes.
|
||||
// Store the temporarily-added transition classes on the element
|
||||
// so that we can avoid overwriting them if the element's class is patched
|
||||
// during the transition.
|
||||
_vtc?: Set<string>
|
||||
[vtcKey]?: Set<string>
|
||||
}
|
||||
|
||||
// DOM Transition is a higher-order-component based on the platform-agnostic
|
||||
|
@ -295,18 +297,18 @@ function NumberOf(val: unknown): number {
|
|||
export function addTransitionClass(el: Element, cls: string) {
|
||||
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
|
||||
;(
|
||||
(el as ElementWithTransition)._vtc ||
|
||||
((el as ElementWithTransition)._vtc = new Set())
|
||||
(el as ElementWithTransition)[vtcKey] ||
|
||||
((el as ElementWithTransition)[vtcKey] = new Set())
|
||||
).add(cls)
|
||||
}
|
||||
|
||||
export function removeTransitionClass(el: Element, cls: string) {
|
||||
cls.split(/\s+/).forEach(c => c && el.classList.remove(c))
|
||||
const { _vtc } = el as ElementWithTransition
|
||||
const _vtc = (el as ElementWithTransition)[vtcKey]
|
||||
if (_vtc) {
|
||||
_vtc.delete(cls)
|
||||
if (!_vtc!.size) {
|
||||
;(el as ElementWithTransition)._vtc = undefined
|
||||
;(el as ElementWithTransition)[vtcKey] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ import {
|
|||
getTransitionInfo,
|
||||
resolveTransitionProps,
|
||||
TransitionPropsValidators,
|
||||
forceReflow
|
||||
forceReflow,
|
||||
vtcKey
|
||||
} from './Transition'
|
||||
import {
|
||||
Fragment,
|
||||
|
@ -29,7 +30,8 @@ import { extend } from '@vue/shared'
|
|||
|
||||
const positionMap = new WeakMap<VNode, DOMRect>()
|
||||
const newPositionMap = new WeakMap<VNode, DOMRect>()
|
||||
|
||||
const moveCbKey = Symbol('_moveCb')
|
||||
const enterCbKey = Symbol('_enterCb')
|
||||
export type TransitionGroupProps = Omit<TransitionProps, 'mode'> & {
|
||||
tag?: string
|
||||
moveClass?: string
|
||||
|
@ -80,13 +82,13 @@ const TransitionGroupImpl: ComponentOptions = {
|
|||
const style = el.style
|
||||
addTransitionClass(el, moveClass)
|
||||
style.transform = style.webkitTransform = style.transitionDuration = ''
|
||||
const cb = ((el as any)._moveCb = (e: TransitionEvent) => {
|
||||
const cb = ((el as any)[moveCbKey] = (e: TransitionEvent) => {
|
||||
if (e && e.target !== el) {
|
||||
return
|
||||
}
|
||||
if (!e || /transform$/.test(e.propertyName)) {
|
||||
el.removeEventListener('transitionend', cb)
|
||||
;(el as any)._moveCb = null
|
||||
;(el as any)[moveCbKey] = null
|
||||
removeTransitionClass(el, moveClass)
|
||||
}
|
||||
})
|
||||
|
@ -162,11 +164,11 @@ export const TransitionGroup = TransitionGroupImpl as unknown as {
|
|||
|
||||
function callPendingCbs(c: VNode) {
|
||||
const el = c.el as any
|
||||
if (el._moveCb) {
|
||||
el._moveCb()
|
||||
if (el[moveCbKey]) {
|
||||
el[moveCbKey]()
|
||||
}
|
||||
if (el._enterCb) {
|
||||
el._enterCb()
|
||||
if (el[enterCbKey]) {
|
||||
el[enterCbKey]()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,8 +200,9 @@ function hasCSSTransform(
|
|||
// all other transition classes applied to ensure only the move class
|
||||
// is applied.
|
||||
const clone = el.cloneNode() as HTMLElement
|
||||
if (el._vtc) {
|
||||
el._vtc.forEach(cls => {
|
||||
const _vtc = el[vtcKey]
|
||||
if (_vtc) {
|
||||
_vtc.forEach(cls => {
|
||||
cls.split(/\s+/).forEach(c => c && clone.classList.remove(c))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,7 +36,9 @@ function onCompositionEnd(e: Event) {
|
|||
}
|
||||
}
|
||||
|
||||
type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }>
|
||||
const assignKey = Symbol('_assign')
|
||||
|
||||
type ModelDirective<T> = ObjectDirective<T & { [assignKey]: AssignerFn }>
|
||||
|
||||
// We are exporting the v-model runtime directly as vnode hooks so that it can
|
||||
// be tree-shaken in case v-model is never used.
|
||||
|
@ -44,7 +46,7 @@ export const vModelText: ModelDirective<
|
|||
HTMLInputElement | HTMLTextAreaElement
|
||||
> = {
|
||||
created(el, { modifiers: { lazy, trim, number } }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
const castToNumber =
|
||||
number || (vnode.props && vnode.props.type === 'number')
|
||||
addEventListener(el, lazy ? 'change' : 'input', e => {
|
||||
|
@ -56,7 +58,7 @@ export const vModelText: ModelDirective<
|
|||
if (castToNumber) {
|
||||
domValue = looseToNumber(domValue)
|
||||
}
|
||||
el._assign(domValue)
|
||||
el[assignKey](domValue)
|
||||
})
|
||||
if (trim) {
|
||||
addEventListener(el, 'change', () => {
|
||||
|
@ -78,7 +80,7 @@ export const vModelText: ModelDirective<
|
|||
el.value = value == null ? '' : value
|
||||
},
|
||||
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
// avoid clearing unresolved text. #2302
|
||||
if ((el as any).composing) return
|
||||
if (document.activeElement === el && el.type !== 'range') {
|
||||
|
@ -106,12 +108,12 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||
// #4096 array checkboxes need to be deep traversed
|
||||
deep: true,
|
||||
created(el, _, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
addEventListener(el, 'change', () => {
|
||||
const modelValue = (el as any)._modelValue
|
||||
const elementValue = getValue(el)
|
||||
const checked = el.checked
|
||||
const assign = el._assign
|
||||
const assign = el[assignKey]
|
||||
if (isArray(modelValue)) {
|
||||
const index = looseIndexOf(modelValue, elementValue)
|
||||
const found = index !== -1
|
||||
|
@ -138,7 +140,7 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||
// set initial checked on mount to wait for true-value/false-value
|
||||
mounted: setChecked,
|
||||
beforeUpdate(el, binding, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
setChecked(el, binding, vnode)
|
||||
}
|
||||
}
|
||||
|
@ -163,13 +165,13 @@ function setChecked(
|
|||
export const vModelRadio: ModelDirective<HTMLInputElement> = {
|
||||
created(el, { value }, vnode) {
|
||||
el.checked = looseEqual(value, vnode.props!.value)
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
addEventListener(el, 'change', () => {
|
||||
el._assign(getValue(el))
|
||||
el[assignKey](getValue(el))
|
||||
})
|
||||
},
|
||||
beforeUpdate(el, { value, oldValue }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
if (value !== oldValue) {
|
||||
el.checked = looseEqual(value, vnode.props!.value)
|
||||
}
|
||||
|
@ -187,7 +189,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|||
.map((o: HTMLOptionElement) =>
|
||||
number ? looseToNumber(getValue(o)) : getValue(o)
|
||||
)
|
||||
el._assign(
|
||||
el[assignKey](
|
||||
el.multiple
|
||||
? isSetModel
|
||||
? new Set(selectedVal)
|
||||
|
@ -195,7 +197,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|||
: selectedVal[0]
|
||||
)
|
||||
})
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
},
|
||||
// set value in mounted & updated because <select> relies on its children
|
||||
// <option>s.
|
||||
|
@ -203,7 +205,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|||
setSelected(el, value)
|
||||
},
|
||||
beforeUpdate(el, _binding, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
},
|
||||
updated(el, { value }) {
|
||||
setSelected(el, value)
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { ObjectDirective } from '@vue/runtime-core'
|
||||
|
||||
export const vShowOldKey = Symbol('_vod')
|
||||
|
||||
interface VShowElement extends HTMLElement {
|
||||
// _vod = vue original display
|
||||
_vod: string
|
||||
[vShowOldKey]: string
|
||||
}
|
||||
|
||||
export const vShow: ObjectDirective<VShowElement> = {
|
||||
beforeMount(el, { value }, { transition }) {
|
||||
el._vod = el.style.display === 'none' ? '' : el.style.display
|
||||
el[vShowOldKey] = el.style.display === 'none' ? '' : el.style.display
|
||||
if (transition && value) {
|
||||
transition.beforeEnter(el)
|
||||
} else {
|
||||
|
@ -41,7 +43,7 @@ export const vShow: ObjectDirective<VShowElement> = {
|
|||
}
|
||||
|
||||
function setDisplay(el: VShowElement, value: unknown): void {
|
||||
el.style.display = value ? el._vod : 'none'
|
||||
el.style.display = value ? el[vShowOldKey] : 'none'
|
||||
}
|
||||
|
||||
// SSR vnode transforms, only used when user includes client-oriented render
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ElementWithTransition } from '../components/Transition'
|
||||
import { ElementWithTransition, vtcKey } from '../components/Transition'
|
||||
|
||||
// compiler should normalize class + :class bindings on the same element
|
||||
// into a single binding ['staticClass', dynamic]
|
||||
|
@ -6,7 +6,7 @@ export function patchClass(el: Element, value: string | null, isSVG: boolean) {
|
|||
// directly setting className should be faster than setAttribute in theory
|
||||
// if this is an element during a transition, take the temporary transition
|
||||
// classes into account.
|
||||
const transitionClasses = (el as ElementWithTransition)._vtc
|
||||
const transitionClasses = (el as ElementWithTransition)[vtcKey]
|
||||
if (transitionClasses) {
|
||||
value = (
|
||||
value ? [value, ...transitionClasses] : [...transitionClasses]
|
||||
|
|
|
@ -30,15 +30,17 @@ export function removeEventListener(
|
|||
el.removeEventListener(event, handler, options)
|
||||
}
|
||||
|
||||
const veiKey = Symbol('_vei')
|
||||
|
||||
export function patchEvent(
|
||||
el: Element & { _vei?: Record<string, Invoker | undefined> },
|
||||
el: Element & { [veiKey]?: Record<string, Invoker | undefined> },
|
||||
rawName: string,
|
||||
prevValue: EventValue | null,
|
||||
nextValue: EventValue | null,
|
||||
instance: ComponentInternalInstance | null = null
|
||||
) {
|
||||
// vei = vue event invokers
|
||||
const invokers = el._vei || (el._vei = {})
|
||||
const invokers = el[veiKey] || (el[veiKey] = {})
|
||||
const existingInvoker = invokers[rawName]
|
||||
if (nextValue && existingInvoker) {
|
||||
// patch
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { isString, hyphenate, capitalize, isArray } from '@vue/shared'
|
||||
import { camelize, warn } from '@vue/runtime-core'
|
||||
import { vShowOldKey } from '../directives/vShow'
|
||||
|
||||
type Style = string | Record<string, string | string[]> | null
|
||||
|
||||
|
@ -29,7 +30,7 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
|
|||
// indicates that the `display` of the element is controlled by `v-show`,
|
||||
// so we always keep the current `display` value regardless of the `style`
|
||||
// value, thus handing over control to `v-show`.
|
||||
if ('_vod' in el) {
|
||||
if (vShowOldKey in el) {
|
||||
style.display = currentDisplay
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// - runtime-core/src/renderer.ts
|
||||
// - compiler-core/src/transforms/transformElement.ts
|
||||
|
||||
import { vtcKey } from '../../runtime-dom/src/components/Transition'
|
||||
import { render, h, ref, nextTick } from '../src'
|
||||
|
||||
describe('SVG support', () => {
|
||||
|
@ -54,7 +55,7 @@ describe('SVG support', () => {
|
|||
|
||||
// set a transition class on the <div> - which is only respected on non-svg
|
||||
// patches
|
||||
;(f2 as any)._vtc = ['baz']
|
||||
;(f2 as any)[vtcKey] = ['baz']
|
||||
cls.value = 'bar'
|
||||
await nextTick()
|
||||
expect(f1.getAttribute('class')).toBe('bar')
|
||||
|
|
Loading…
Reference in New Issue