mirror of https://github.com/vuejs/core.git
fix(hydration): force hydration for v-bind with .prop modifier
ref #7490
This commit is contained in:
parent
34b5a5da4a
commit
364f319d21
|
@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock(\\"input\\", {
|
return (_openBlock(), _createElementBlock(\\"input\\", {
|
||||||
\\"foo-value\\": model,
|
\\"foo-value\\": model,
|
||||||
\\"onUpdate:fooValue\\": $event => ((model) = $event)
|
\\"onUpdate:fooValue\\": $event => ((model) = $event)
|
||||||
}, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
|
}, null, 40 /* PROPS, NEED_HYDRATION */, [\\"foo-value\\", \\"onUpdate:fooValue\\"]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1089,7 +1089,7 @@ describe('compiler: element transform', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('HYDRATE_EVENTS', () => {
|
test('NEED_HYDRATION for v-on', () => {
|
||||||
// ignore click events (has dedicated fast path)
|
// ignore click events (has dedicated fast path)
|
||||||
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
|
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
|
@ -1108,12 +1108,24 @@ describe('compiler: element transform', () => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(node2.patchFlag).toBe(
|
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
|
// #5870
|
||||||
test('HYDRATE_EVENTS on dynamic component', () => {
|
test('NEED_HYDRATION on dynamic component', () => {
|
||||||
const { node } = parseWithElementTransform(
|
const { node } = parseWithElementTransform(
|
||||||
`<component :is="foo" @input="foo" />`,
|
`<component :is="foo" @input="foo" />`,
|
||||||
{
|
{
|
||||||
|
@ -1123,7 +1135,7 @@ describe('compiler: element transform', () => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(node.patchFlag).toBe(
|
expect(node.patchFlag).toBe(
|
||||||
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
|
genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION])
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -550,7 +550,7 @@ export function buildProps(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// directives
|
// directives
|
||||||
const { name, arg, exp, loc } = prop
|
const { name, arg, exp, loc, modifiers } = prop
|
||||||
const isVBind = name === 'bind'
|
const isVBind = name === 'bind'
|
||||||
const isVOn = name === 'on'
|
const isVOn = name === 'on'
|
||||||
|
|
||||||
|
@ -678,6 +678,11 @@ export function buildProps(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// force hydration for v-bind with .prop modifier
|
||||||
|
if (isVBind && modifiers.includes('prop')) {
|
||||||
|
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||||
|
}
|
||||||
|
|
||||||
const directiveTransform = context.directiveTransforms[name]
|
const directiveTransform = context.directiveTransforms[name]
|
||||||
if (directiveTransform) {
|
if (directiveTransform) {
|
||||||
// has built-in directive transform.
|
// has built-in directive transform.
|
||||||
|
@ -743,12 +748,12 @@ export function buildProps(
|
||||||
patchFlag |= PatchFlags.PROPS
|
patchFlag |= PatchFlags.PROPS
|
||||||
}
|
}
|
||||||
if (hasHydrationEventBinding) {
|
if (hasHydrationEventBinding) {
|
||||||
patchFlag |= PatchFlags.HYDRATE_EVENTS
|
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!shouldUseBlock &&
|
!shouldUseBlock &&
|
||||||
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
(patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) &&
|
||||||
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
||||||
) {
|
) {
|
||||||
patchFlag |= PatchFlags.NEED_PATCH
|
patchFlag |= PatchFlags.NEED_PATCH
|
||||||
|
|
|
@ -272,7 +272,7 @@ describe('compiler-dom: transform v-on', () => {
|
||||||
// should not treat cached handler as dynamicProp, so it should have no
|
// should not treat cached handler as dynamicProp, so it should have no
|
||||||
// dynamicProps flags and only the hydration flag
|
// dynamicProps flags and only the hydration flag
|
||||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||||
genFlagText(PatchFlags.HYDRATE_EVENTS)
|
genFlagText(PatchFlags.NEED_HYDRATION)
|
||||||
)
|
)
|
||||||
expect(prop).toMatchObject({
|
expect(prop).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
|
|
|
@ -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', () => {
|
test('force hydrate input v-model with non-string value bindings', () => {
|
||||||
const { container } = mountWithHydration(
|
const { container } = mountWithHydration(
|
||||||
'<input type="checkbox" value="true">',
|
'<input type="checkbox" value="true">',
|
||||||
|
|
|
@ -477,13 +477,13 @@ describe('vnode', () => {
|
||||||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
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 hoist = createVNode('div')
|
||||||
const vnode =
|
const vnode =
|
||||||
(openBlock(),
|
(openBlock(),
|
||||||
createBlock('div', null, [
|
createBlock('div', null, [
|
||||||
hoist,
|
hoist,
|
||||||
createVNode('div', null, 'text', PatchFlags.HYDRATE_EVENTS)
|
createVNode('div', null, 'text', PatchFlags.NEED_HYDRATION)
|
||||||
]))
|
]))
|
||||||
expect(vnode.dynamicChildren).toStrictEqual([])
|
expect(vnode.dynamicChildren).toStrictEqual([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -334,13 +334,15 @@ export function createHydrationFunctions(
|
||||||
if (
|
if (
|
||||||
forcePatch ||
|
forcePatch ||
|
||||||
!optimized ||
|
!optimized ||
|
||||||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.HYDRATE_EVENTS)
|
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
||||||
) {
|
) {
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
if (
|
if (
|
||||||
(forcePatch &&
|
(forcePatch &&
|
||||||
(key.endsWith('value') || key === 'indeterminate')) ||
|
(key.endsWith('value') || key === 'indeterminate')) ||
|
||||||
(isOn(key) && !isReservedProp(key))
|
(isOn(key) && !isReservedProp(key)) ||
|
||||||
|
// force hydrate v-bind with .prop modifiers
|
||||||
|
key[0] === '.'
|
||||||
) {
|
) {
|
||||||
patchProp(
|
patchProp(
|
||||||
el,
|
el,
|
||||||
|
|
|
@ -2395,7 +2395,7 @@ export function traverseStaticChildren(n1: VNode, n2: VNode, shallow = false) {
|
||||||
const c1 = ch1[i] as VNode
|
const c1 = ch1[i] as VNode
|
||||||
let c2 = ch2[i] as VNode
|
let c2 = ch2[i] as VNode
|
||||||
if (c2.shapeFlag & ShapeFlags.ELEMENT && !c2.dynamicChildren) {
|
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 = ch2[i] = cloneIfMounted(ch2[i] as VNode)
|
||||||
c2.el = c1.el
|
c2.el = c1.el
|
||||||
}
|
}
|
||||||
|
|
|
@ -489,7 +489,7 @@ function createBaseVNode(
|
||||||
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
|
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
|
||||||
// the EVENTS flag is only for hydration and if it is the only flag, the
|
// 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 should not be considered dynamic due to handler caching.
|
||||||
vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
|
vnode.patchFlag !== PatchFlags.NEED_HYDRATION
|
||||||
) {
|
) {
|
||||||
currentBlock.push(vnode)
|
currentBlock.push(vnode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,10 +57,11 @@ export const enum PatchFlags {
|
||||||
FULL_PROPS = 1 << 4,
|
FULL_PROPS = 1 << 4,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates an element with event listeners (which need to be attached
|
* Indicates an element that requires props hydration
|
||||||
* during 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.
|
* Indicates a fragment whose children order doesn't change.
|
||||||
|
@ -131,7 +132,7 @@ export const PatchFlagNames: Record<PatchFlags, string> = {
|
||||||
[PatchFlags.STYLE]: `STYLE`,
|
[PatchFlags.STYLE]: `STYLE`,
|
||||||
[PatchFlags.PROPS]: `PROPS`,
|
[PatchFlags.PROPS]: `PROPS`,
|
||||||
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
|
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
|
||||||
[PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
|
[PatchFlags.NEED_HYDRATION]: `NEED_HYDRATION`,
|
||||||
[PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
|
[PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
|
||||||
[PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
|
[PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
|
||||||
[PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
|
[PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
|
||||||
|
|
Loading…
Reference in New Issue