Skip to content

refactor: use symbol for private properties #8681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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