Skip to content

Commit

Permalink
refactor: use symbol for private properties (vuejs#8681)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alfred-Skyblue authored Aug 22, 2023
1 parent 02c6924 commit 2ffe3d5
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 54 deletions.
31 changes: 17 additions & 14 deletions packages/runtime-core/src/components/BaseTransition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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])
},
Expand All @@ -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) {
Expand All @@ -407,7 +410,7 @@ export function resolveTransitionHooks(
if (hooks.delayedLeave) {
hooks.delayedLeave()
}
el._enterCb = undefined
el[enterCbKey] = undefined
})
if (hook) {
callAsyncHook(hook, [el, done])
Expand All @@ -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()
Expand All @@ -435,7 +438,7 @@ export function resolveTransitionHooks(
} else {
callHook(onAfterLeave, [el])
}
el._leaveCb = undefined
el[leaveCbKey] = undefined
if (leavingVNodesCache[key] === vnode) {
delete leavingVNodesCache[key]
}
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-dom/__tests__/patchClass.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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')
})
Expand Down
12 changes: 7 additions & 5 deletions packages/runtime-dom/src/components/Transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
}
Expand Down
23 changes: 13 additions & 10 deletions packages/runtime-dom/src/components/TransitionGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
getTransitionInfo,
resolveTransitionProps,
TransitionPropsValidators,
forceReflow
forceReflow,
vtcKey
} from './Transition'
import {
Fragment,
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
})
Expand Down Expand Up @@ -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]()
}
}

Expand Down Expand Up @@ -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))
})
}
Expand Down
28 changes: 15 additions & 13 deletions packages/runtime-dom/src/directives/vModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,17 @@ 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.
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 => {
Expand All @@ -56,7 +58,7 @@ export const vModelText: ModelDirective<
if (castToNumber) {
domValue = looseToNumber(domValue)
}
el._assign(domValue)
el[assignKey](domValue)
})
if (trim) {
addEventListener(el, 'change', () => {
Expand All @@ -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') {
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}
Expand All @@ -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)
}
Expand All @@ -187,23 +189,23 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
.map((o: HTMLOptionElement) =>
number ? looseToNumber(getValue(o)) : getValue(o)
)
el._assign(
el[assignKey](
el.multiple
? isSetModel
? new Set(selectedVal)
: selectedVal
: selectedVal[0]
)
})
el._assign = getModelAssigner(vnode)
el[assignKey] = getModelAssigner(vnode)
},
// set value in mounted & updated because <select> relies on its children
// <option>s.
mounted(el, { value }) {
setSelected(el, value)
},
beforeUpdate(el, _binding, vnode) {
el._assign = getModelAssigner(vnode)
el[assignKey] = getModelAssigner(vnode)
},
updated(el, { value }) {
setSelected(el, value)
Expand Down
8 changes: 5 additions & 3 deletions packages/runtime-dom/src/directives/vShow.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-dom/src/modules/class.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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]
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]
Expand Down
6 changes: 4 additions & 2 deletions packages/runtime-dom/src/modules/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 2ffe3d5

Please sign in to comment.