Skip to content

Commit

Permalink
perf: improve scoped slots change detection accuracy (#9371)
Browse files Browse the repository at this point in the history
Ensure that state mutations that only affect parent scope only trigger parent update and does not affect child components with only scoped slots.
  • Loading branch information
yyx990803 authored Jan 26, 2019
1 parent 770c6ed commit f219bed
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 167 deletions.
1 change: 1 addition & 0 deletions flow/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ declare type ASTElement = {
transitionMode?: string | null;
slotName?: ?string;
slotTarget?: ?string;
slotTargetDynamic?: boolean;
slotScope?: ?string;
scopedSlots?: { [name: string]: ASTElement };

Expand Down
3 changes: 2 additions & 1 deletion src/compiler/codegen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,12 @@ function genScopedSlots (
slots: { [key: string]: ASTElement },
state: CodegenState
): string {
const hasDynamicKeys = Object.keys(slots).some(key => slots[key].slotTargetDynamic)
return `scopedSlots:_u([${
Object.keys(slots).map(key => {
return genScopedSlot(key, slots[key], state)
}).join(',')
}])`
}]${hasDynamicKeys ? `,true` : ``})`
}

function genScopedSlot (
Expand Down
18 changes: 11 additions & 7 deletions src/compiler/parser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ function processSlotContent (el) {
const slotTarget = getBindingAttr(el, 'slot')
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
// preserve slot as an attribute for native shadow DOM compat
// only for non-scoped slots.
if (el.tag !== 'template' && !el.slotScope) {
Expand All @@ -607,8 +608,10 @@ function processSlotContent (el) {
)
}
}
el.slotTarget = getSlotName(slotBinding)
el.slotScope = slotBinding.value
const { name, dynamic } = getSlotName(slotBinding)
el.slotTarget = name
el.slotTargetDynamic = dynamic
el.slotScope = slotBinding.value || `_` // force it into a scoped slot for perf
}
} else {
// v-slot on component, denotes default slot
Expand Down Expand Up @@ -637,10 +640,11 @@ function processSlotContent (el) {
}
// add the component's children to its default slot
const slots = el.scopedSlots || (el.scopedSlots = {})
const target = getSlotName(slotBinding)
const slotContainer = slots[target] = createASTElement('template', [], el)
const { name, dynamic } = getSlotName(slotBinding)
const slotContainer = slots[name] = createASTElement('template', [], el)
slotContainer.slotTargetDynamic = dynamic
slotContainer.children = el.children
slotContainer.slotScope = slotBinding.value
slotContainer.slotScope = slotBinding.value || `_`
// remove children as they are returned from scopedSlots now
el.children = []
// mark el non-plain so data gets generated
Expand All @@ -664,9 +668,9 @@ function getSlotName (binding) {
}
return dynamicKeyRE.test(name)
// dynamic [name]
? name.slice(1, -1)
? { name: name.slice(1, -1), dynamic: true }
// static name
: `"${name}"`
: { name: `"${name}"`, dynamic: false }
}

// handle <slot/> outlets
Expand Down
20 changes: 15 additions & 5 deletions src/core/instance/lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,22 @@ export function updateChildComponent (
}

// determine whether component has slot children
// we need to do this before overwriting $options._renderChildren
const hasChildren = !!(
// we need to do this before overwriting $options._renderChildren.

// check if there are dynamic scopedSlots (hand-written or compiled but with
// dynamic slot names). Static scoped slots compiled from template has the
// "$stable" marker.
const hasDynamicScopedSlot = !!(
(parentVnode.data.scopedSlots && !parentVnode.data.scopedSlots.$stable) ||
(vm.$scopedSlots !== emptyObject && !vm.$scopedSlots.$stable)
)
// Any static slot children from the parent may have changed during parent's
// update. Dynamic scoped slots may also have changed. In such cases, a forced
// update is necessary to ensure correctness.
const needsForceUpdate = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
parentVnode.data.scopedSlots || // has new scoped slots
vm.$scopedSlots !== emptyObject // has old scoped slots
hasDynamicScopedSlot
)

vm.$options._parentVnode = parentVnode
Expand Down Expand Up @@ -268,7 +278,7 @@ export function updateChildComponent (
updateComponentListeners(vm, listeners, oldListeners)

// resolve slots + force update if has children
if (hasChildren) {
if (needsForceUpdate) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
vm.$forceUpdate()
}
Expand Down
7 changes: 4 additions & 3 deletions src/core/instance/render-helpers/resolve-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ function isWhitespace (node: VNode): boolean {

export function resolveScopedSlots (
fns: ScopedSlotsData, // see flow/vnode
hasDynamicKeys?: boolean,
res?: Object
): { [key: string]: Function } {
res = res || {}
): { [key: string]: Function, $stable: boolean } {
res = res || { $stable: !hasDynamicKeys }
for (let i = 0; i < fns.length; i++) {
const slot = fns[i]
if (Array.isArray(slot)) {
resolveScopedSlots(slot, res)
resolveScopedSlots(slot, hasDynamicKeys, res)
} else {
res[slot.key] = slot.fn
}
Expand Down
3 changes: 2 additions & 1 deletion src/core/vdom/helpers/normalize-scoped-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function normalizeScopedSlots (
} else {
res = {}
for (const key in slots) {
if (slots[key]) {
if (slots[key] && key[0] !== '$') {
res[key] = normalizeScopedSlot(slots[key])
}
}
Expand All @@ -26,6 +26,7 @@ export function normalizeScopedSlots (
}
}
res._normalized = true
res.$stable = slots && slots.$stable
return res
}

Expand Down
Loading

0 comments on commit f219bed

Please sign in to comment.