parent
44e6da1402
commit
aab99abd28
@ -719,6 +719,23 @@ describe('compiler: transform component slots', () => {
|
|||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('generate flag on forwarded slots', () => {
|
||||||
|
const { slots } = parseWithSlots(`<Comp><slot/></Comp>`)
|
||||||
|
expect(slots).toMatchObject({
|
||||||
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: { content: `default` },
|
||||||
|
value: { type: NodeTypes.JS_FUNCTION_EXPRESSION }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: { content: `_` },
|
||||||
|
value: { content: `3` } // forwarded
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
test('error on extraneous children w/ named default slot', () => {
|
test('error on extraneous children w/ named default slot', () => {
|
||||||
const onError = jest.fn()
|
const onError = jest.fn()
|
||||||
|
@ -33,6 +33,7 @@ import {
|
|||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
|
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
|
||||||
import { parseForExpression, createForLoopParams } from './vFor'
|
import { parseForExpression, createForLoopParams } from './vFor'
|
||||||
|
import { SlotFlags } from '@vue/shared/src'
|
||||||
|
|
||||||
const defaultFallback = createSimpleExpression(`undefined`, false)
|
const defaultFallback = createSimpleExpression(`undefined`, false)
|
||||||
|
|
||||||
@ -321,13 +322,19 @@ export function buildSlots(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slotFlag = hasDynamicSlots
|
||||||
|
? SlotFlags.DYNAMIC
|
||||||
|
: hasForwardedSlots(node.children)
|
||||||
|
? SlotFlags.FORWARDED
|
||||||
|
: SlotFlags.STABLE
|
||||||
|
|
||||||
let slots = createObjectExpression(
|
let slots = createObjectExpression(
|
||||||
slotsProperties.concat(
|
slotsProperties.concat(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
`_`,
|
`_`,
|
||||||
// 2 = compiled but dynamic = can skip normalization, but must run diff
|
// 2 = compiled but dynamic = can skip normalization, but must run diff
|
||||||
// 1 = compiled and static = can skip normalization AND diff as optimized
|
// 1 = compiled and static = can skip normalization AND diff as optimized
|
||||||
createSimpleExpression(hasDynamicSlots ? `2` : `1`, false)
|
createSimpleExpression('' + slotFlag, false)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
loc
|
loc
|
||||||
@ -354,3 +361,19 @@ function buildDynamicSlot(
|
|||||||
createObjectProperty(`fn`, fn)
|
createObjectProperty(`fn`, fn)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasForwardedSlots(children: TemplateChildNode[]): boolean {
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i]
|
||||||
|
if (child.type === NodeTypes.ELEMENT) {
|
||||||
|
if (
|
||||||
|
child.tagType === ElementTypes.SLOT ||
|
||||||
|
(child.tagType === ElementTypes.ELEMENT &&
|
||||||
|
hasForwardedSlots(child.children))
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
ShapeFlags,
|
ShapeFlags,
|
||||||
extend,
|
extend,
|
||||||
def
|
def,
|
||||||
|
SlotFlags
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { isKeepAlive } from './components/KeepAlive'
|
import { isKeepAlive } from './components/KeepAlive'
|
||||||
@ -27,24 +28,25 @@ export type InternalSlots = {
|
|||||||
|
|
||||||
export type Slots = Readonly<InternalSlots>
|
export type Slots = Readonly<InternalSlots>
|
||||||
|
|
||||||
export const enum CompiledSlotTypes {
|
|
||||||
STATIC = 1,
|
|
||||||
DYNAMIC = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RawSlots = {
|
export type RawSlots = {
|
||||||
[name: string]: unknown
|
[name: string]: unknown
|
||||||
// manual render fn hint to skip forced children updates
|
// manual render fn hint to skip forced children updates
|
||||||
$stable?: boolean
|
$stable?: boolean
|
||||||
// internal, for tracking slot owner instance. This is attached during
|
/**
|
||||||
// normalizeChildren when the component vnode is created.
|
* for tracking slot owner instance. This is attached during
|
||||||
|
* normalizeChildren when the component vnode is created.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
_ctx?: ComponentInternalInstance | null
|
_ctx?: ComponentInternalInstance | null
|
||||||
// internal, indicates compiler generated slots
|
/**
|
||||||
// we use a reserved property instead of a vnode patchFlag because the slots
|
* indicates compiler generated slots
|
||||||
// object may be directly passed down to a child component in a manual
|
* we use a reserved property instead of a vnode patchFlag because the slots
|
||||||
// render function, and the optimization hint need to be on the slot object
|
* object may be directly passed down to a child component in a manual
|
||||||
// itself to be preserved.
|
* render function, and the optimization hint need to be on the slot object
|
||||||
_?: CompiledSlotTypes
|
* itself to be preserved.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_?: SlotFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
||||||
@ -141,8 +143,8 @@ export const updateSlots = (
|
|||||||
// Parent was HMR updated so slot content may have changed.
|
// Parent was HMR updated so slot content may have changed.
|
||||||
// force update slots and mark instance for hmr as well
|
// force update slots and mark instance for hmr as well
|
||||||
extend(slots, children as Slots)
|
extend(slots, children as Slots)
|
||||||
} else if (type === CompiledSlotTypes.STATIC) {
|
} else if (type === SlotFlags.STABLE) {
|
||||||
// compiled AND static.
|
// compiled AND stable.
|
||||||
// no need to update, and skip stale slots removal.
|
// no need to update, and skip stale slots removal.
|
||||||
needDeletionCheck = false
|
needDeletionCheck = false
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Data } from '../component'
|
import { Data } from '../component'
|
||||||
import { Slots, RawSlots, CompiledSlotTypes } from '../componentSlots'
|
import { Slots, RawSlots } from '../componentSlots'
|
||||||
import {
|
import {
|
||||||
VNodeArrayChildren,
|
VNodeArrayChildren,
|
||||||
openBlock,
|
openBlock,
|
||||||
@ -7,7 +7,7 @@ import {
|
|||||||
Fragment,
|
Fragment,
|
||||||
VNode
|
VNode
|
||||||
} from '../vnode'
|
} from '../vnode'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags, SlotFlags } from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +39,7 @@ export function renderSlot(
|
|||||||
Fragment,
|
Fragment,
|
||||||
{ key: props.key },
|
{ key: props.key },
|
||||||
slot ? slot(props) : fallback ? fallback() : [],
|
slot ? slot(props) : fallback ? fallback() : [],
|
||||||
(slots as RawSlots)._ === CompiledSlotTypes.STATIC
|
(slots as RawSlots)._ === SlotFlags.STABLE
|
||||||
? PatchFlags.STABLE_FRAGMENT
|
? PatchFlags.STABLE_FRAGMENT
|
||||||
: PatchFlags.BAIL
|
: PatchFlags.BAIL
|
||||||
)
|
)
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
normalizeClass,
|
normalizeClass,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
PatchFlags,
|
PatchFlags,
|
||||||
ShapeFlags
|
ShapeFlags,
|
||||||
|
SlotFlags
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
@ -542,10 +543,22 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
type = ShapeFlags.SLOTS_CHILDREN
|
type = ShapeFlags.SLOTS_CHILDREN
|
||||||
if (!(children as RawSlots)._ && !(InternalObjectKey in children!)) {
|
const slotFlag = (children as RawSlots)._
|
||||||
|
if (!slotFlag && !(InternalObjectKey in children!)) {
|
||||||
// if slots are not normalized, attach context instance
|
// if slots are not normalized, attach context instance
|
||||||
// (compiled / normalized slots already have context)
|
// (compiled / normalized slots already have context)
|
||||||
;(children as RawSlots)._ctx = currentRenderingInstance
|
;(children as RawSlots)._ctx = currentRenderingInstance
|
||||||
|
} else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
|
||||||
|
// a child component receives forwarded slots from the parent.
|
||||||
|
// its slot type is determined by its parent's slot type.
|
||||||
|
if (
|
||||||
|
currentRenderingInstance.vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS
|
||||||
|
) {
|
||||||
|
;(children as RawSlots)._ = SlotFlags.DYNAMIC
|
||||||
|
vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
|
||||||
|
} else {
|
||||||
|
;(children as RawSlots)._ = SlotFlags.STABLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isFunction(children)) {
|
} else if (isFunction(children)) {
|
||||||
|
@ -3,6 +3,7 @@ import { makeMap } from './makeMap'
|
|||||||
export { makeMap }
|
export { makeMap }
|
||||||
export * from './patchFlags'
|
export * from './patchFlags'
|
||||||
export * from './shapeFlags'
|
export * from './shapeFlags'
|
||||||
|
export * from './slotFlags'
|
||||||
export * from './globalsWhitelist'
|
export * from './globalsWhitelist'
|
||||||
export * from './codeframe'
|
export * from './codeframe'
|
||||||
export * from './mockWarn'
|
export * from './mockWarn'
|
||||||
|
21
packages/shared/src/slotFlags.ts
Normal file
21
packages/shared/src/slotFlags.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export const enum SlotFlags {
|
||||||
|
/**
|
||||||
|
* Stable slots that only reference slot props or context state. The slot
|
||||||
|
* can fully capture its own dependencies so when passed down the parent won't
|
||||||
|
* need to force the child to update.
|
||||||
|
*/
|
||||||
|
STABLE = 1,
|
||||||
|
/**
|
||||||
|
* Slots that reference scope variables (v-for or an outer slot prop), or
|
||||||
|
* has conditional structure (v-if, v-for). The parent will need to force
|
||||||
|
* the child to update because the slot does not fully capture its dependencies.
|
||||||
|
*/
|
||||||
|
DYNAMIC = 2,
|
||||||
|
/**
|
||||||
|
* <slot/> being forwarded into a child component. Whether the parent needs
|
||||||
|
* to update the child is dependent on what kind of slots the parent itself
|
||||||
|
* received. This has to be refined at runtime, when the child's vnode
|
||||||
|
* is being created (in `normalizeChildren`)
|
||||||
|
*/
|
||||||
|
FORWARDED = 3
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user