wip: exclude legacy slots from $scopedSlots

This commit is contained in:
Evan You 2021-05-05 11:06:04 -04:00
parent b14de6c3f8
commit 7f93c76b96
7 changed files with 80 additions and 28 deletions

View File

@ -352,6 +352,11 @@ export interface FunctionExpression extends Node {
* withScopeId() wrapper * withScopeId() wrapper
*/ */
isSlot: boolean isSlot: boolean
/**
* __COMPAT__ only, indicates a slot function that should be excluded from
* the legacy $scopedSlots instance property.
*/
isNonScopedSlot?: boolean
} }
export interface ConditionalExpression extends Node { export interface ConditionalExpression extends Node {

View File

@ -878,6 +878,9 @@ function genFunctionExpression(
push(`}`) push(`}`)
} }
if (isSlot) { if (isSlot) {
if (__COMPAT__ && node.isNonScopedSlot) {
push(`, undefined, true`)
}
push(`)`) push(`)`)
} }
} }

View File

@ -129,11 +129,6 @@ export function buildSlots(
const slotsProperties: Property[] = [] const slotsProperties: Property[] = []
const dynamicSlots: (ConditionalExpression | CallExpression)[] = [] const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
const buildDefaultSlotProperty = (
props: ExpressionNode | undefined,
children: TemplateChildNode[]
) => createObjectProperty(`default`, buildSlotFn(props, children, loc))
// If the slot is inside a v-for or another v-slot, force it to be dynamic // If the slot is inside a v-for or another v-slot, force it to be dynamic
// since it likely uses a scope variable. // since it likely uses a scope variable.
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0 let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
@ -302,6 +297,17 @@ export function buildSlots(
} }
if (!onComponentSlot) { if (!onComponentSlot) {
const buildDefaultSlotProperty = (
props: ExpressionNode | undefined,
children: TemplateChildNode[]
) => {
const fn = buildSlotFn(props, children, loc)
if (__COMPAT__) {
fn.isNonScopedSlot = true
}
return createObjectProperty(`default`, fn)
}
if (!hasTemplateSlots) { if (!hasTemplateSlots) {
// implicit default slot (on component) // implicit default slot (on component)
slotsProperties.push(buildDefaultSlotProperty(undefined, children)) slotsProperties.push(buildDefaultSlotProperty(undefined, children))

View File

@ -36,7 +36,7 @@ import {
} from './renderHelpers' } from './renderHelpers'
import { resolveFilter } from '../helpers/resolveAssets' import { resolveFilter } from '../helpers/resolveAssets'
import { resolveMergedOptions } from '../componentOptions' import { resolveMergedOptions } from '../componentOptions'
import { Slots } from '../componentSlots' import { InternalSlots, Slots } from '../componentSlots'
export type LegacyPublicInstance = ComponentPublicInstance & export type LegacyPublicInstance = ComponentPublicInstance &
LegacyPublicProperties LegacyPublicProperties
@ -103,7 +103,14 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
$scopedSlots: i => { $scopedSlots: i => {
assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i) assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i)
return __DEV__ ? shallowReadonly(i.slots) : i.slots const res: InternalSlots = {}
for (const key in i.slots) {
const fn = i.slots[key]!
if (!(fn as any)._nonScoped) {
res[key] = fn
}
}
return res
}, },
$on: i => on.bind(null, i), $on: i => on.bind(null, i),

View File

@ -281,6 +281,7 @@ function convertLegacySlots(vnode: VNode): VNode {
for (const key in slots) { for (const key in slots) {
const slotChildren = slots[key] const slotChildren = slots[key]
slots[key] = () => slotChildren slots[key] = () => slotChildren
slots[key]._nonScoped = true
} }
} }
} }

View File

@ -61,7 +61,8 @@ export const withScopeId = (_id: string) => withCtx
*/ */
export function withCtx( export function withCtx(
fn: Function, fn: Function,
ctx: ComponentInternalInstance | null = currentRenderingInstance ctx: ComponentInternalInstance | null = currentRenderingInstance,
isNonScopedSlot?: boolean // __COMPAT__ only
) { ) {
if (!ctx) return fn if (!ctx) return fn
const renderFnWithContext = (...args: any[]) => { const renderFnWithContext = (...args: any[]) => {
@ -83,5 +84,8 @@ export function withCtx(
// this is used in vnode.ts -> normalizeChildren() to set the slot // this is used in vnode.ts -> normalizeChildren() to set the slot
// rendering flag. // rendering flag.
renderFnWithContext._c = true renderFnWithContext._c = true
if (__COMPAT__ && isNonScopedSlot) {
renderFnWithContext._nonScoped = true
}
return renderFnWithContext return renderFnWithContext
} }

View File

@ -251,30 +251,56 @@ test('INSTANCE_LISTENERS', () => {
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
test('INSTANCE_SCOPED_SLOTS', () => { describe('INSTANCE_SCOPED_SLOTS', () => {
let slots: Slots test('explicit usage', () => {
new Vue({ let slots: Slots
template: `<child v-slot="{ msg }">{{ msg }}</child>`, new Vue({
components: { template: `<child v-slot="{ msg }">{{ msg }}</child>`,
child: { components: {
compatConfig: { RENDER_FUNCTION: false }, child: {
render() { compatConfig: { RENDER_FUNCTION: false },
slots = this.$scopedSlots render() {
slots = this.$scopedSlots
}
} }
} }
} }).$mount()
}).$mount()
expect(slots!.default!({ msg: 'hi' })).toMatchObject([ expect(slots!.default!({ msg: 'hi' })).toMatchObject([
{ {
type: Text, type: Text,
children: 'hi' children: 'hi'
} }
]) ])
expect( expect(
deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
).toHaveBeenWarned() ).toHaveBeenWarned()
})
test('should not include legacy slot usage in $scopedSlots', () => {
let normalSlots: Slots
let scopedSlots: Slots
new Vue({
template: `<child><div>default</div></child>`,
components: {
child: {
compatConfig: { RENDER_FUNCTION: false },
render() {
normalSlots = this.$slots
scopedSlots = this.$scopedSlots
}
}
}
}).$mount()
expect('default' in normalSlots!).toBe(true)
expect('default' in scopedSlots!).toBe(false)
expect(
deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
).toHaveBeenWarned()
})
}) })
test('INSTANCE_ATTR_CLASS_STYLE', () => { test('INSTANCE_ATTR_CLASS_STYLE', () => {