Skip to content
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

fix(runtime-core): make manually written render function work with optimized slots #3523

Merged
merged 1 commit into from
Apr 1, 2021
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
61 changes: 60 additions & 1 deletion packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
defineComponent,
withCtx,
renderSlot,
onBeforeUnmount
onBeforeUnmount,
createTextVNode,
SetupContext,
createApp
} from '@vue/runtime-test'
import { PatchFlags, SlotFlags } from '@vue/shared'

Expand Down Expand Up @@ -517,4 +520,60 @@ describe('renderer: optimized mode', () => {
expect(spyA).toHaveBeenCalledTimes(1)
expect(spyB).toHaveBeenCalledTimes(1)
})

// #2893
test('manually rendering the optimized slots should allow subsequent updates to exit the optimized mode correctly', async () => {
const state = ref(0)

const CompA = {
setup(props: any, { slots }: SetupContext) {
return () => {
return (
openBlock(),
createBlock('div', null, [renderSlot(slots, 'default')])
)
}
}
}

const Wrapper = {
setup(props: any, { slots }: SetupContext) {
// use the manually written render function to rendering the optimized slots,
// which should make subsequent updates exit the optimized mode correctly
return () => {
return slots.default!()[state.value]
}
}
}

const app = createApp({
setup() {
return () => {
return (
openBlock(),
createBlock(Wrapper, null, {
default: withCtx(() => [
createVNode(CompA, null, {
default: withCtx(() => [createTextVNode('Hello')]),
_: 1 /* STABLE */
}),
createVNode(CompA, null, {
default: withCtx(() => [createTextVNode('World')]),
_: 1 /* STABLE */
})
]),
_: 1 /* STABLE */
})
)
}
}
})

app.mount(root)
expect(inner(root)).toBe('<div>Hello</div>')

state.value = 1
await nextTick()
expect(inner(root)).toBe('<div>World</div>')
})
})
12 changes: 10 additions & 2 deletions packages/runtime-core/src/componentSlots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ export const initSlots = (

export const updateSlots = (
instance: ComponentInternalInstance,
children: VNodeNormalizedChildren
children: VNodeNormalizedChildren,
optimized: boolean
) => {
const { vnode, slots } = instance
let needDeletionCheck = true
Expand All @@ -143,14 +144,21 @@ export const updateSlots = (
// Parent was HMR updated so slot content may have changed.
// force update slots and mark instance for hmr as well
extend(slots, children as Slots)
} else if (type === SlotFlags.STABLE) {
} else if (optimized && type === SlotFlags.STABLE) {
// compiled AND stable.
// no need to update, and skip stale slots removal.
needDeletionCheck = false
} else {
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
// normalization.
extend(slots, children as Slots)
// #2893
// when rendering the optimized slots by manually written render function,
// we need to delete the `slots._` flag if necessary to make subsequent updates reliable,
// i.e. let the `renderSlot` create the bailed Fragment
if (!optimized && type === SlotFlags.STABLE) {
delete slots._
}
}
} else {
needDeletionCheck = !(children as RawSlots).$stable
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1576,7 +1576,7 @@ function baseCreateRenderer(
instance.vnode = nextVNode
instance.next = null
updateProps(instance, nextVNode.props, prevProps, optimized)
updateSlots(instance, nextVNode.children)
updateSlots(instance, nextVNode.children, optimized)

pauseTracking()
// props update may have triggered pre-flush watchers.
Expand Down