Skip to content

Commit

Permalink
fix(runtime-core): fix render function + optimized slot edge case (#3523
Browse files Browse the repository at this point in the history
)

fix #2893

Manually rendering the optimized slots should allow subsequent updates to exit the optimization mode correctly
  • Loading branch information
HcySunYang authored Apr 1, 2021
1 parent c90fb94 commit 995d76b
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 4 deletions.
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

0 comments on commit 995d76b

Please sign in to comment.