mirror of https://github.com/vuejs/core.git
refactor: update logical child handling and optimize hydration state management
This commit is contained in:
parent
4bc9c8b85c
commit
679a67dbba
|
@ -179,8 +179,8 @@ function genInsertionState(
|
||||||
: anchor === -1 // -1 indicates prepend
|
: anchor === -1 // -1 indicates prepend
|
||||||
? `0` // runtime anchor value for prepend
|
? `0` // runtime anchor value for prepend
|
||||||
: append // -2 indicates append
|
: append // -2 indicates append
|
||||||
? // null or number > 0 for append
|
? // null or anchor > 0 for append
|
||||||
// number > 0 is used for locate the previous static node during hydration
|
// anchor > 0 is the logical index of append node - used for locate node during hydration
|
||||||
anchor === 0
|
anchor === 0
|
||||||
? 'null'
|
? 'null'
|
||||||
: `${anchor}`
|
: `${anchor}`
|
||||||
|
|
|
@ -60,6 +60,7 @@ export const transformChildren: NodeTransform = (node, context) => {
|
||||||
function processDynamicChildren(context: TransformContext<ElementNode>) {
|
function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
let prevDynamics: IRDynamicInfo[] = []
|
let prevDynamics: IRDynamicInfo[] = []
|
||||||
let staticCount = 0
|
let staticCount = 0
|
||||||
|
let dynamicCount = 0
|
||||||
const children = context.dynamic.children
|
const children = context.dynamic.children
|
||||||
|
|
||||||
for (const [index, child] of children.entries()) {
|
for (const [index, child] of children.entries()) {
|
||||||
|
@ -77,6 +78,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
} else {
|
} else {
|
||||||
registerInsertion(prevDynamics, context, -1 /* prepend */)
|
registerInsertion(prevDynamics, context, -1 /* prepend */)
|
||||||
}
|
}
|
||||||
|
dynamicCount += prevDynamics.length
|
||||||
prevDynamics = []
|
prevDynamics = []
|
||||||
}
|
}
|
||||||
staticCount++
|
staticCount++
|
||||||
|
@ -84,7 +86,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevDynamics.length) {
|
if (prevDynamics.length) {
|
||||||
registerInsertion(prevDynamics, context, staticCount, true)
|
registerInsertion(prevDynamics, context, dynamicCount + staticCount, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,14 +151,13 @@ export const createFor = (
|
||||||
if (!parentAnchor || (parentAnchor && !isComment(parentAnchor, ']'))) {
|
if (!parentAnchor || (parentAnchor && !isComment(parentAnchor, ']'))) {
|
||||||
throw new Error(`v-for fragment anchor node was not found.`)
|
throw new Error(`v-for fragment anchor node was not found.`)
|
||||||
}
|
}
|
||||||
|
// the lastLogicalChild is the fragment start anchor; replacing it with end anchor
|
||||||
// $lastLogicalChild is the fragment start anchor; replacing it with end anchor
|
|
||||||
// can avoid the call to locateEndAnchor within locateChildByLogicalIndex
|
// can avoid the call to locateEndAnchor within locateChildByLogicalIndex
|
||||||
if (_insertionParent && _insertionParent!.$lastLogicalChild) {
|
if (_insertionParent && _insertionParent!.$llc) {
|
||||||
;(parentAnchor as any as ChildItem).$idx = (
|
;(parentAnchor as any as ChildItem).$idx = (
|
||||||
_insertionParent!.$lastLogicalChild as ChildItem
|
_insertionParent!.$llc as ChildItem
|
||||||
).$idx
|
).$idx
|
||||||
_insertionParent.$lastLogicalChild = parentAnchor
|
_insertionParent.$llc = parentAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -69,11 +69,11 @@ export function isValidBlock(block: Block): boolean {
|
||||||
|
|
||||||
export function insert(
|
export function insert(
|
||||||
block: Block,
|
block: Block,
|
||||||
parent: ParentNode & { $prependAnchor?: Node | null },
|
parent: ParentNode & { $fc?: Node | null },
|
||||||
anchor: Node | null | 0 = null, // 0 means prepend
|
anchor: Node | null | 0 = null, // 0 means prepend
|
||||||
parentSuspense?: any, // TODO Suspense
|
parentSuspense?: any, // TODO Suspense
|
||||||
): void {
|
): void {
|
||||||
anchor = anchor === 0 ? parent.$prependAnchor || _child(parent) : anchor
|
anchor = anchor === 0 ? parent.$fc || _child(parent) : anchor
|
||||||
if (block instanceof Node) {
|
if (block instanceof Node) {
|
||||||
if (!isHydrating) {
|
if (!isHydrating) {
|
||||||
// only apply transition on Element nodes
|
// only apply transition on Element nodes
|
||||||
|
|
|
@ -44,11 +44,11 @@ function performHydration<T>(
|
||||||
// optimize anchor cache lookup
|
// optimize anchor cache lookup
|
||||||
;(Comment.prototype as any).$fe = undefined
|
;(Comment.prototype as any).$fe = undefined
|
||||||
;(Node.prototype as any).$pns = undefined
|
;(Node.prototype as any).$pns = undefined
|
||||||
;(Node.prototype as any).$uc = undefined
|
|
||||||
;(Node.prototype as any).$idx = undefined
|
;(Node.prototype as any).$idx = undefined
|
||||||
;(Node.prototype as any).$prevDynamicCount = undefined
|
;(Node.prototype as any).$llc = undefined
|
||||||
;(Node.prototype as any).$anchorCount = undefined
|
;(Node.prototype as any).$lpn = undefined
|
||||||
;(Node.prototype as any).$appendIndex = undefined
|
;(Node.prototype as any).$lan = undefined
|
||||||
|
;(Node.prototype as any).$lin = undefined
|
||||||
|
|
||||||
isOptimized = true
|
isOptimized = true
|
||||||
}
|
}
|
||||||
|
@ -146,67 +146,37 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nextNode(node: Node): Node | null {
|
||||||
|
return isComment(node, '[')
|
||||||
|
? locateEndAnchor(node as Anchor)!.nextSibling
|
||||||
|
: node.nextSibling
|
||||||
|
}
|
||||||
|
|
||||||
function locateHydrationNodeImpl(): void {
|
function locateHydrationNodeImpl(): void {
|
||||||
let node: Node | null
|
let node: Node | null
|
||||||
if (insertionAnchor !== undefined) {
|
if (insertionAnchor !== undefined) {
|
||||||
const {
|
const { $lpn: lastPrepend, $lan: lastAppend, firstChild } = insertionParent!
|
||||||
$prevDynamicCount: prevDynamicCount = 0,
|
|
||||||
$appendIndex: appendIndex,
|
|
||||||
$anchorCount: anchorCount = 0,
|
|
||||||
} = insertionParent!
|
|
||||||
// prepend
|
// prepend
|
||||||
if (insertionAnchor === 0) {
|
if (insertionAnchor === 0) {
|
||||||
// use prevDynamicCount as logical index to locate the hydration node
|
node = insertionParent!.$lpn = lastPrepend
|
||||||
node =
|
? nextNode(lastPrepend)
|
||||||
prevDynamicCount === 0 &&
|
: firstChild
|
||||||
currentHydrationNode!.parentNode === insertionParent
|
|
||||||
? currentHydrationNode
|
|
||||||
: locateChildByLogicalIndex(insertionParent!, prevDynamicCount)!
|
|
||||||
}
|
}
|
||||||
// insert
|
// insert
|
||||||
else if (insertionAnchor instanceof Node) {
|
else if (insertionAnchor instanceof Node) {
|
||||||
// handling insertion anchors:
|
const { $lin: lastInsertedNode } = insertionAnchor as ChildItem
|
||||||
// 1. first encounter: use insertionAnchor itself as the hydration node
|
node = (insertionAnchor as ChildItem).$lin = lastInsertedNode
|
||||||
// 2. subsequent: use node following the insertionAnchor as the hydration node
|
? nextNode(lastInsertedNode)
|
||||||
// used count tracks how many times insertionAnchor has been used, ensuring
|
: insertionAnchor
|
||||||
// consecutive insert operations locate the correct hydration node.
|
|
||||||
let { $idx, $uc: usedCount } = insertionAnchor as ChildItem
|
|
||||||
if (usedCount !== undefined) {
|
|
||||||
node = locateChildByLogicalIndex(
|
|
||||||
insertionParent!,
|
|
||||||
$idx + usedCount + 1,
|
|
||||||
)!
|
|
||||||
usedCount++
|
|
||||||
} else {
|
|
||||||
insertionParent!.$lastLogicalChild = node = insertionAnchor
|
|
||||||
// first use of this anchor: it doesn't consume the next child
|
|
||||||
// so we track unique anchor appearances for later offset correction
|
|
||||||
insertionParent!.$anchorCount = anchorCount + 1
|
|
||||||
usedCount = 0
|
|
||||||
}
|
|
||||||
;(insertionAnchor as ChildItem).$uc = usedCount
|
|
||||||
}
|
}
|
||||||
// append
|
// append
|
||||||
else {
|
else {
|
||||||
if (appendIndex !== null && appendIndex !== undefined) {
|
node = insertionParent!.$lan = lastAppend
|
||||||
node = locateChildByLogicalIndex(insertionParent!, appendIndex + 1)!
|
? nextNode(lastAppend)
|
||||||
} else {
|
: insertionAnchor === null
|
||||||
if (insertionAnchor === null) {
|
? firstChild
|
||||||
node =
|
: locateChildByLogicalIndex(insertionParent!, insertionAnchor)!
|
||||||
currentHydrationNode!.parentNode === insertionParent
|
|
||||||
? currentHydrationNode
|
|
||||||
: locateChildByLogicalIndex(insertionParent!, 0)!
|
|
||||||
} else {
|
|
||||||
node = locateChildByLogicalIndex(
|
|
||||||
insertionParent!,
|
|
||||||
prevDynamicCount + insertionAnchor,
|
|
||||||
)!
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
insertionParent!.$appendIndex = (node as ChildItem).$idx
|
|
||||||
}
|
|
||||||
|
|
||||||
insertionParent!.$prevDynamicCount = prevDynamicCount + 1
|
|
||||||
} else {
|
} else {
|
||||||
node = currentHydrationNode
|
node = currentHydrationNode
|
||||||
if (insertionParent && (!node || node.parentNode !== insertionParent)) {
|
if (insertionParent && (!node || node.parentNode !== insertionParent)) {
|
||||||
|
|
|
@ -142,13 +142,13 @@ export function locateChildByLogicalIndex(
|
||||||
parent: InsertionParent,
|
parent: InsertionParent,
|
||||||
logicalIndex: number,
|
logicalIndex: number,
|
||||||
): Node | null {
|
): Node | null {
|
||||||
let child = (parent.$lastLogicalChild || parent.firstChild) as ChildItem
|
let child = (parent.$llc || parent.firstChild) as ChildItem
|
||||||
let fromIndex = child.$idx || 0
|
let fromIndex = child.$idx || 0
|
||||||
|
|
||||||
while (child) {
|
while (child) {
|
||||||
if (fromIndex === logicalIndex) {
|
if (fromIndex === logicalIndex) {
|
||||||
child.$idx = logicalIndex
|
child.$idx = logicalIndex
|
||||||
return (parent.$lastLogicalChild = child)
|
return (parent.$llc = child)
|
||||||
}
|
}
|
||||||
|
|
||||||
child = (
|
child = (
|
||||||
|
|
|
@ -386,8 +386,7 @@ export function optimizePropertyLookup(): void {
|
||||||
const proto = Element.prototype as any
|
const proto = Element.prototype as any
|
||||||
proto.$transition = undefined
|
proto.$transition = undefined
|
||||||
proto.$key = undefined
|
proto.$key = undefined
|
||||||
proto.$prependAnchor = proto.$evtclick = undefined
|
proto.$fc = proto.$evtclick = undefined
|
||||||
proto.$idx = undefined
|
|
||||||
proto.$root = false
|
proto.$root = false
|
||||||
proto.$html =
|
proto.$html =
|
||||||
proto.$txt =
|
proto.$txt =
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
import { isHydrating } from './dom/hydration'
|
import { isHydrating } from './dom/hydration'
|
||||||
export type ChildItem = ChildNode & {
|
export type ChildItem = ChildNode & {
|
||||||
|
// logical index, used during hydration to locate the node
|
||||||
$idx: number
|
$idx: number
|
||||||
// used count as an anchor
|
// last inserted node
|
||||||
$uc?: number
|
$lin?: Node | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InsertionParent = ParentNode & {
|
export type InsertionParent = ParentNode & {
|
||||||
$prependAnchor?: Node | null
|
// cache the first child for potential consecutive prepends
|
||||||
|
$fc?: Node | null
|
||||||
|
|
||||||
/**
|
|
||||||
* hydration-specific properties
|
|
||||||
*/
|
|
||||||
// hydrated dynamic children count so far
|
|
||||||
$prevDynamicCount?: number
|
|
||||||
// number of unique insertion anchors that have appeared
|
|
||||||
$anchorCount?: number
|
|
||||||
// last append index
|
|
||||||
$appendIndex?: number | null
|
|
||||||
// last located logical child
|
// last located logical child
|
||||||
$lastLogicalChild?: Node | null
|
$llc?: Node | null
|
||||||
|
// last prepend node
|
||||||
|
$lpn?: Node | null
|
||||||
|
// last append node
|
||||||
|
$lan?: Node | null
|
||||||
}
|
}
|
||||||
export let insertionParent: InsertionParent | undefined
|
export let insertionParent: InsertionParent | undefined
|
||||||
export let insertionAnchor: Node | 0 | undefined | null
|
export let insertionAnchor: Node | 0 | undefined | null
|
||||||
|
@ -29,7 +26,7 @@ export let insertionAnchor: Node | 0 | undefined | null
|
||||||
* insertion on client-side render, and used for node adoption during hydration.
|
* insertion on client-side render, and used for node adoption during hydration.
|
||||||
*/
|
*/
|
||||||
export function setInsertionState(
|
export function setInsertionState(
|
||||||
parent: ParentNode & { $prependAnchor?: Node | null },
|
parent: ParentNode & { $fc?: Node | null },
|
||||||
anchor?: Node | 0 | null | number,
|
anchor?: Node | 0 | null | number,
|
||||||
): void {
|
): void {
|
||||||
insertionParent = parent
|
insertionParent = parent
|
||||||
|
@ -37,19 +34,13 @@ export function setInsertionState(
|
||||||
if (anchor !== undefined) {
|
if (anchor !== undefined) {
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
insertionAnchor = anchor as Node
|
insertionAnchor = anchor as Node
|
||||||
// when the setInsertionState is called for the first time, reset $lastLogicalChild,
|
|
||||||
// in order to reuse it in locateChildByLogicalIndex
|
|
||||||
if (insertionParent.$prevDynamicCount === undefined) {
|
|
||||||
insertionParent!.$lastLogicalChild = null
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// special handling append anchor value to null
|
// special handling append anchor value to null
|
||||||
insertionAnchor =
|
insertionAnchor =
|
||||||
typeof anchor === 'number' && anchor > 0 ? null : (anchor as Node)
|
typeof anchor === 'number' && anchor > 0 ? null : (anchor as Node)
|
||||||
|
|
||||||
// track the first child for potential future use
|
if (anchor === 0 && !parent.$fc) {
|
||||||
if (anchor === 0 && !parent.$prependAnchor) {
|
parent.$fc = parent.firstChild
|
||||||
parent.$prependAnchor = parent.firstChild
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue