Skip to content

Commit

Permalink
fix(hydration): force hydration for v-bind with .prop modifier
Browse files Browse the repository at this point in the history
ref #7490
  • Loading branch information
yyx990803 committed Nov 10, 2023
1 parent 34b5a5d commit 364f319
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(\\"input\\", {
\\"foo-value\\": model,
\\"onUpdate:fooValue\\": $event => ((model) = $event)
}, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
}, null, 40 /* PROPS, NEED_HYDRATION */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
}
}"
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,7 @@ describe('compiler: element transform', () => {
})
})

test('HYDRATE_EVENTS', () => {
test('NEED_HYDRATION for v-on', () => {
// ignore click events (has dedicated fast path)
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
directiveTransforms: {
Expand All @@ -1108,12 +1108,24 @@ describe('compiler: element transform', () => {
}
)
expect(node2.patchFlag).toBe(
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
)
})

test('NEED_HYDRATION for v-bind.prop', () => {
const { node } = parseWithBind(`<div v-bind:id.prop="id" />`)
expect(node.patchFlag).toBe(
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
)

const { node: node2 } = parseWithBind(`<div .id="id" />`)
expect(node2.patchFlag).toBe(
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
)
})

// #5870
test('HYDRATE_EVENTS on dynamic component', () => {
test('NEED_HYDRATION on dynamic component', () => {
const { node } = parseWithElementTransform(
`<component :is="foo" @input="foo" />`,
{
Expand All @@ -1123,7 +1135,7 @@ describe('compiler: element transform', () => {
}
)
expect(node.patchFlag).toBe(
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
)
})
})
Expand Down
11 changes: 8 additions & 3 deletions packages/compiler-core/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ export function buildProps(
)
} else {
// directives
const { name, arg, exp, loc } = prop
const { name, arg, exp, loc, modifiers } = prop
const isVBind = name === 'bind'
const isVOn = name === 'on'

Expand Down Expand Up @@ -678,6 +678,11 @@ export function buildProps(
continue
}

// force hydration for v-bind with .prop modifier
if (isVBind && modifiers.includes('prop')) {
patchFlag |= PatchFlags.NEED_HYDRATION
}

const directiveTransform = context.directiveTransforms[name]
if (directiveTransform) {
// has built-in directive transform.
Expand Down Expand Up @@ -743,12 +748,12 @@ export function buildProps(
patchFlag |= PatchFlags.PROPS
}
if (hasHydrationEventBinding) {
patchFlag |= PatchFlags.HYDRATE_EVENTS
patchFlag |= PatchFlags.NEED_HYDRATION
}
}
if (
!shouldUseBlock &&
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
(patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) &&
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
) {
patchFlag |= PatchFlags.NEED_PATCH
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-dom/__tests__/transforms/vOn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ describe('compiler-dom: transform v-on', () => {
// should not treat cached handler as dynamicProp, so it should have no
// dynamicProps flags and only the hydration flag
expect((root as any).children[0].codegenNode.patchFlag).toBe(
genFlagText(PatchFlags.HYDRATE_EVENTS)
genFlagText(PatchFlags.NEED_HYDRATION)
)
expect(prop).toMatchObject({
key: {
Expand Down
12 changes: 12 additions & 0 deletions packages/runtime-core/__tests__/hydration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,18 @@ describe('SSR hydration', () => {
)
})

test('force hydrate prop with `.prop` modifier', () => {
const { container } = mountWithHydration(
'<input type="checkbox" :indeterminate.prop="true">',
() =>
h('input', {
type: 'checkbox',
'.indeterminate': true
})
)
expect((container.firstChild! as any).indeterminate).toBe(true)
})

test('force hydrate input v-model with non-string value bindings', () => {
const { container } = mountWithHydration(
'<input type="checkbox" value="true">',
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/__tests__/vnode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,13 @@ describe('vnode', () => {
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
})

test('should not track vnodes with only HYDRATE_EVENTS flag', () => {
test('should not track vnodes with only NEED_HYDRATION flag', () => {
const hoist = createVNode('div')
const vnode =
(openBlock(),
createBlock('div', null, [
hoist,
createVNode('div', null, 'text', PatchFlags.HYDRATE_EVENTS)
createVNode('div', null, 'text', PatchFlags.NEED_HYDRATION)
]))
expect(vnode.dynamicChildren).toStrictEqual([])
})
Expand Down
6 changes: 4 additions & 2 deletions packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,15 @@ export function createHydrationFunctions(
if (
forcePatch ||
!optimized ||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.HYDRATE_EVENTS)
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
) {
for (const key in props) {
if (
(forcePatch &&
(key.endsWith('value') || key === 'indeterminate')) ||
(isOn(key) && !isReservedProp(key))
(isOn(key) && !isReservedProp(key)) ||
// force hydrate v-bind with .prop modifiers
key[0] === '.'
) {
patchProp(
el,
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 @@ -2395,7 +2395,7 @@ export function traverseStaticChildren(n1: VNode, n2: VNode, shallow = false) {
const c1 = ch1[i] as VNode
let c2 = ch2[i] as VNode
if (c2.shapeFlag & ShapeFlags.ELEMENT && !c2.dynamicChildren) {
if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.HYDRATE_EVENTS) {
if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.NEED_HYDRATION) {
c2 = ch2[i] = cloneIfMounted(ch2[i] as VNode)
c2.el = c1.el
}
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ function createBaseVNode(
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
vnode.patchFlag !== PatchFlags.NEED_HYDRATION
) {
currentBlock.push(vnode)
}
Expand Down
9 changes: 5 additions & 4 deletions packages/shared/src/patchFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ export const enum PatchFlags {
FULL_PROPS = 1 << 4,

/**
* Indicates an element with event listeners (which need to be attached
* during hydration)
* Indicates an element that requires props hydration
* (but not necessarily patching)
* e.g. event listeners & v-bind with prop modifier
*/
HYDRATE_EVENTS = 1 << 5,
NEED_HYDRATION = 1 << 5,

/**
* Indicates a fragment whose children order doesn't change.
Expand Down Expand Up @@ -131,7 +132,7 @@ export const PatchFlagNames: Record<PatchFlags, string> = {
[PatchFlags.STYLE]: `STYLE`,
[PatchFlags.PROPS]: `PROPS`,
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
[PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
[PatchFlags.NEED_HYDRATION]: `NEED_HYDRATION`,
[PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
[PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
[PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
Expand Down

0 comments on commit 364f319

Please sign in to comment.