mirror of https://github.com/vuejs/vue.git
perf: improve scoped slots change detection accuracy (#9371)
Ensure that state mutations that only affect parent scope only trigger parent update and does not affect child components with only scoped slots.
This commit is contained in:
parent
770c6ed64f
commit
f219bedae8
|
@ -119,6 +119,7 @@ declare type ASTElement = {
|
||||||
transitionMode?: string | null;
|
transitionMode?: string | null;
|
||||||
slotName?: ?string;
|
slotName?: ?string;
|
||||||
slotTarget?: ?string;
|
slotTarget?: ?string;
|
||||||
|
slotTargetDynamic?: boolean;
|
||||||
slotScope?: ?string;
|
slotScope?: ?string;
|
||||||
scopedSlots?: { [name: string]: ASTElement };
|
scopedSlots?: { [name: string]: ASTElement };
|
||||||
|
|
||||||
|
|
|
@ -354,11 +354,12 @@ function genScopedSlots (
|
||||||
slots: { [key: string]: ASTElement },
|
slots: { [key: string]: ASTElement },
|
||||||
state: CodegenState
|
state: CodegenState
|
||||||
): string {
|
): string {
|
||||||
|
const hasDynamicKeys = Object.keys(slots).some(key => slots[key].slotTargetDynamic)
|
||||||
return `scopedSlots:_u([${
|
return `scopedSlots:_u([${
|
||||||
Object.keys(slots).map(key => {
|
Object.keys(slots).map(key => {
|
||||||
return genScopedSlot(key, slots[key], state)
|
return genScopedSlot(key, slots[key], state)
|
||||||
}).join(',')
|
}).join(',')
|
||||||
}])`
|
}]${hasDynamicKeys ? `,true` : ``})`
|
||||||
}
|
}
|
||||||
|
|
||||||
function genScopedSlot (
|
function genScopedSlot (
|
||||||
|
|
|
@ -586,6 +586,7 @@ function processSlotContent (el) {
|
||||||
const slotTarget = getBindingAttr(el, 'slot')
|
const slotTarget = getBindingAttr(el, 'slot')
|
||||||
if (slotTarget) {
|
if (slotTarget) {
|
||||||
el.slotTarget = slotTarget === '""' ? '"default"' : 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
|
// preserve slot as an attribute for native shadow DOM compat
|
||||||
// only for non-scoped slots.
|
// only for non-scoped slots.
|
||||||
if (el.tag !== 'template' && !el.slotScope) {
|
if (el.tag !== 'template' && !el.slotScope) {
|
||||||
|
@ -607,8 +608,10 @@ function processSlotContent (el) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
el.slotTarget = getSlotName(slotBinding)
|
const { name, dynamic } = getSlotName(slotBinding)
|
||||||
el.slotScope = slotBinding.value
|
el.slotTarget = name
|
||||||
|
el.slotTargetDynamic = dynamic
|
||||||
|
el.slotScope = slotBinding.value || `_` // force it into a scoped slot for perf
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// v-slot on component, denotes default slot
|
// v-slot on component, denotes default slot
|
||||||
|
@ -637,10 +640,11 @@ function processSlotContent (el) {
|
||||||
}
|
}
|
||||||
// add the component's children to its default slot
|
// add the component's children to its default slot
|
||||||
const slots = el.scopedSlots || (el.scopedSlots = {})
|
const slots = el.scopedSlots || (el.scopedSlots = {})
|
||||||
const target = getSlotName(slotBinding)
|
const { name, dynamic } = getSlotName(slotBinding)
|
||||||
const slotContainer = slots[target] = createASTElement('template', [], el)
|
const slotContainer = slots[name] = createASTElement('template', [], el)
|
||||||
|
slotContainer.slotTargetDynamic = dynamic
|
||||||
slotContainer.children = el.children
|
slotContainer.children = el.children
|
||||||
slotContainer.slotScope = slotBinding.value
|
slotContainer.slotScope = slotBinding.value || `_`
|
||||||
// remove children as they are returned from scopedSlots now
|
// remove children as they are returned from scopedSlots now
|
||||||
el.children = []
|
el.children = []
|
||||||
// mark el non-plain so data gets generated
|
// mark el non-plain so data gets generated
|
||||||
|
@ -664,9 +668,9 @@ function getSlotName (binding) {
|
||||||
}
|
}
|
||||||
return dynamicKeyRE.test(name)
|
return dynamicKeyRE.test(name)
|
||||||
// dynamic [name]
|
// dynamic [name]
|
||||||
? name.slice(1, -1)
|
? { name: name.slice(1, -1), dynamic: true }
|
||||||
// static name
|
// static name
|
||||||
: `"${name}"`
|
: { name: `"${name}"`, dynamic: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle <slot/> outlets
|
// handle <slot/> outlets
|
||||||
|
|
|
@ -224,12 +224,22 @@ export function updateChildComponent (
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine whether component has slot children
|
// determine whether component has slot children
|
||||||
// we need to do this before overwriting $options._renderChildren
|
// we need to do this before overwriting $options._renderChildren.
|
||||||
const hasChildren = !!(
|
|
||||||
|
// 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
|
renderChildren || // has new static slots
|
||||||
vm.$options._renderChildren || // has old static slots
|
vm.$options._renderChildren || // has old static slots
|
||||||
parentVnode.data.scopedSlots || // has new scoped slots
|
hasDynamicScopedSlot
|
||||||
vm.$scopedSlots !== emptyObject // has old scoped slots
|
|
||||||
)
|
)
|
||||||
|
|
||||||
vm.$options._parentVnode = parentVnode
|
vm.$options._parentVnode = parentVnode
|
||||||
|
@ -268,7 +278,7 @@ export function updateChildComponent (
|
||||||
updateComponentListeners(vm, listeners, oldListeners)
|
updateComponentListeners(vm, listeners, oldListeners)
|
||||||
|
|
||||||
// resolve slots + force update if has children
|
// resolve slots + force update if has children
|
||||||
if (hasChildren) {
|
if (needsForceUpdate) {
|
||||||
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
|
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
|
||||||
vm.$forceUpdate()
|
vm.$forceUpdate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,13 +51,14 @@ function isWhitespace (node: VNode): boolean {
|
||||||
|
|
||||||
export function resolveScopedSlots (
|
export function resolveScopedSlots (
|
||||||
fns: ScopedSlotsData, // see flow/vnode
|
fns: ScopedSlotsData, // see flow/vnode
|
||||||
|
hasDynamicKeys?: boolean,
|
||||||
res?: Object
|
res?: Object
|
||||||
): { [key: string]: Function } {
|
): { [key: string]: Function, $stable: boolean } {
|
||||||
res = res || {}
|
res = res || { $stable: !hasDynamicKeys }
|
||||||
for (let i = 0; i < fns.length; i++) {
|
for (let i = 0; i < fns.length; i++) {
|
||||||
const slot = fns[i]
|
const slot = fns[i]
|
||||||
if (Array.isArray(slot)) {
|
if (Array.isArray(slot)) {
|
||||||
resolveScopedSlots(slot, res)
|
resolveScopedSlots(slot, hasDynamicKeys, res)
|
||||||
} else {
|
} else {
|
||||||
res[slot.key] = slot.fn
|
res[slot.key] = slot.fn
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function normalizeScopedSlots (
|
||||||
} else {
|
} else {
|
||||||
res = {}
|
res = {}
|
||||||
for (const key in slots) {
|
for (const key in slots) {
|
||||||
if (slots[key]) {
|
if (slots[key] && key[0] !== '$') {
|
||||||
res[key] = normalizeScopedSlot(slots[key])
|
res[key] = normalizeScopedSlot(slots[key])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ export function normalizeScopedSlots (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res._normalized = true
|
res._normalized = true
|
||||||
|
res.$stable = slots && slots.$stable
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -633,131 +633,69 @@ describe('Component scoped slot', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2.6 new slot syntax
|
// 2.6 new slot syntax
|
||||||
if (process.env.NEW_SLOT_SYNTAX) {
|
describe('v-slot syntax', () => {
|
||||||
describe('v-slot syntax', () => {
|
const Foo = {
|
||||||
const Foo = {
|
render(h) {
|
||||||
render(h) {
|
return h('div', [
|
||||||
return h('div', [
|
this.$scopedSlots.default && this.$scopedSlots.default('from foo default'),
|
||||||
this.$scopedSlots.default && this.$scopedSlots.default('from foo default'),
|
this.$scopedSlots.one && this.$scopedSlots.one('from foo one'),
|
||||||
this.$scopedSlots.one && this.$scopedSlots.one('from foo one'),
|
this.$scopedSlots.two && this.$scopedSlots.two('from foo two')
|
||||||
this.$scopedSlots.two && this.$scopedSlots.two('from foo two')
|
])
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Bar = {
|
const Bar = {
|
||||||
render(h) {
|
render(h) {
|
||||||
return this.$scopedSlots.default && this.$scopedSlots.default('from bar')
|
return this.$scopedSlots.default && this.$scopedSlots.default('from bar')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Baz = {
|
const Baz = {
|
||||||
render(h) {
|
render(h) {
|
||||||
return this.$scopedSlots.default && this.$scopedSlots.default('from baz')
|
return this.$scopedSlots.default && this.$scopedSlots.default('from baz')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toNamed = (syntax, name) => syntax[0] === '#'
|
const toNamed = (syntax, name) => syntax[0] === '#'
|
||||||
? `#${name}` // shorthand
|
? `#${name}` // shorthand
|
||||||
: `${syntax}:${name}` // full syntax
|
: `${syntax}:${name}` // full syntax
|
||||||
|
|
||||||
function runSuite(syntax) {
|
function runSuite(syntax) {
|
||||||
it('default slot', () => {
|
it('default slot', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `<foo ${syntax}="foo">{{ foo }}<div>{{ foo }}</div></foo>`,
|
template: `<foo ${syntax}="foo">{{ foo }}<div>{{ foo }}</div></foo>`,
|
||||||
components: { Foo }
|
components: { Foo }
|
||||||
}).$mount()
|
}).$mount()
|
||||||
expect(vm.$el.innerHTML).toBe(`from foo default<div>from foo default</div>`)
|
expect(vm.$el.innerHTML).toBe(`from foo default<div>from foo default</div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('nested default slots', () => {
|
it('nested default slots', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `
|
template: `
|
||||||
<foo ${syntax}="foo">
|
<foo ${syntax}="foo">
|
||||||
<bar ${syntax}="bar">
|
<bar ${syntax}="bar">
|
||||||
<baz ${syntax}="baz">
|
<baz ${syntax}="baz">
|
||||||
{{ foo }} | {{ bar }} | {{ baz }}
|
{{ foo }} | {{ bar }} | {{ baz }}
|
||||||
</baz>
|
</baz>
|
||||||
</bar>
|
</bar>
|
||||||
</foo>
|
</foo>
|
||||||
`,
|
`,
|
||||||
components: { Foo, Bar, Baz }
|
components: { Foo, Bar, Baz }
|
||||||
}).$mount()
|
}).$mount()
|
||||||
expect(vm.$el.innerHTML.trim()).toBe(`from foo default | from bar | from baz`)
|
expect(vm.$el.innerHTML.trim()).toBe(`from foo default | from bar | from baz`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('named slots', () => {
|
it('named slots', () => {
|
||||||
const vm = new Vue({
|
|
||||||
template: `
|
|
||||||
<foo>
|
|
||||||
<template ${toNamed(syntax, 'default')}="foo">
|
|
||||||
{{ foo }}
|
|
||||||
</template>
|
|
||||||
<template ${toNamed(syntax, 'one')}="one">
|
|
||||||
{{ one }}
|
|
||||||
</template>
|
|
||||||
<template ${toNamed(syntax, 'two')}="two">
|
|
||||||
{{ two }}
|
|
||||||
</template>
|
|
||||||
</foo>
|
|
||||||
`,
|
|
||||||
components: { Foo }
|
|
||||||
}).$mount()
|
|
||||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo default from foo one from foo two`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('nested + named + default slots', () => {
|
|
||||||
const vm = new Vue({
|
|
||||||
template: `
|
|
||||||
<foo>
|
|
||||||
<template ${toNamed(syntax, 'one')}="one">
|
|
||||||
<bar ${syntax}="bar">
|
|
||||||
{{ one }} {{ bar }}
|
|
||||||
</bar>
|
|
||||||
</template>
|
|
||||||
<template ${toNamed(syntax, 'two')}="two">
|
|
||||||
<baz ${syntax}="baz">
|
|
||||||
{{ two }} {{ baz }}
|
|
||||||
</baz>
|
|
||||||
</template>
|
|
||||||
</foo>
|
|
||||||
`,
|
|
||||||
components: { Foo, Bar, Baz }
|
|
||||||
}).$mount()
|
|
||||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one from bar from foo two from baz`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should warn v-slot usage on non-component elements', () => {
|
|
||||||
const vm = new Vue({
|
|
||||||
template: `<div ${syntax}="foo"/>`
|
|
||||||
}).$mount()
|
|
||||||
expect(`v-slot can only be used on components or <template>`).toHaveBeenWarned()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should warn mixed usage', () => {
|
|
||||||
const vm = new Vue({
|
|
||||||
template: `<foo><bar slot="one" slot-scope="bar" ${syntax}="bar"></bar></foo>`,
|
|
||||||
components: { Foo, Bar }
|
|
||||||
}).$mount()
|
|
||||||
expect(`Unexpected mixed usage of different slot syntaxes`).toHaveBeenWarned()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// run tests for both full syntax and shorthand
|
|
||||||
runSuite('v-slot')
|
|
||||||
runSuite('#default')
|
|
||||||
|
|
||||||
it('shorthand named slots', () => {
|
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `
|
template: `
|
||||||
<foo>
|
<foo>
|
||||||
<template #default="foo">
|
<template ${toNamed(syntax, 'default')}="foo">
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
</template>
|
</template>
|
||||||
<template #one="one">
|
<template ${toNamed(syntax, 'one')}="one">
|
||||||
{{ one }}
|
{{ one }}
|
||||||
</template>
|
</template>
|
||||||
<template #two="two">
|
<template ${toNamed(syntax, 'two')}="two">
|
||||||
{{ two }}
|
{{ two }}
|
||||||
</template>
|
</template>
|
||||||
</foo>
|
</foo>
|
||||||
|
@ -767,62 +705,165 @@ describe('Component scoped slot', () => {
|
||||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo default from foo one from foo two`)
|
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo default from foo one from foo two`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should warn mixed root-default and named slots', () => {
|
it('nested + named + default slots', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `
|
template: `
|
||||||
<foo #default="foo">
|
<foo>
|
||||||
{{ foo }}
|
<template ${toNamed(syntax, 'one')}="one">
|
||||||
<template #one="one">
|
<bar ${syntax}="bar">
|
||||||
{{ one }}
|
{{ one }} {{ bar }}
|
||||||
|
</bar>
|
||||||
|
</template>
|
||||||
|
<template ${toNamed(syntax, 'two')}="two">
|
||||||
|
<baz ${syntax}="baz">
|
||||||
|
{{ two }} {{ baz }}
|
||||||
|
</baz>
|
||||||
</template>
|
</template>
|
||||||
</foo>
|
</foo>
|
||||||
`,
|
`,
|
||||||
components: { Foo }
|
components: { Foo, Bar, Baz }
|
||||||
}).$mount()
|
}).$mount()
|
||||||
expect(`default slot should also use <template>`).toHaveBeenWarned()
|
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one from bar from foo two from baz`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shorthand without scope variable', () => {
|
it('should warn v-slot usage on non-component elements', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `
|
template: `<div ${syntax}="foo"/>`
|
||||||
<foo>
|
|
||||||
<template #one>one</template>
|
|
||||||
<template #two>two</template>
|
|
||||||
</foo>
|
|
||||||
`,
|
|
||||||
components: { Foo }
|
|
||||||
}).$mount()
|
}).$mount()
|
||||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`onetwo`)
|
expect(`v-slot can only be used on components or <template>`).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shorthand named slots on root', () => {
|
it('should warn mixed usage', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `
|
template: `<foo><bar slot="one" slot-scope="bar" ${syntax}="bar"></bar></foo>`,
|
||||||
<foo #one="one">
|
components: { Foo, Bar }
|
||||||
|
}).$mount()
|
||||||
|
expect(`Unexpected mixed usage of different slot syntaxes`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// run tests for both full syntax and shorthand
|
||||||
|
runSuite('v-slot')
|
||||||
|
runSuite('#default')
|
||||||
|
|
||||||
|
it('shorthand named slots', () => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `
|
||||||
|
<foo>
|
||||||
|
<template #default="foo">
|
||||||
|
{{ foo }}
|
||||||
|
</template>
|
||||||
|
<template #one="one">
|
||||||
{{ one }}
|
{{ one }}
|
||||||
</foo>
|
</template>
|
||||||
`,
|
<template #two="two">
|
||||||
components: { Foo }
|
{{ two }}
|
||||||
}).$mount()
|
</template>
|
||||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one`)
|
</foo>
|
||||||
})
|
`,
|
||||||
|
components: { Foo }
|
||||||
it('dynamic slot name', () => {
|
}).$mount()
|
||||||
const vm = new Vue({
|
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo default from foo one from foo two`)
|
||||||
data: {
|
|
||||||
a: 'one',
|
|
||||||
b: 'two'
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<foo>
|
|
||||||
<template #[a]="one">{{ one }} </template>
|
|
||||||
<template v-slot:[b]="two">{{ two }}</template>
|
|
||||||
</foo>
|
|
||||||
`,
|
|
||||||
components: { Foo }
|
|
||||||
}).$mount()
|
|
||||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one from foo two`)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
it('should warn mixed root-default and named slots', () => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `
|
||||||
|
<foo #default="foo">
|
||||||
|
{{ foo }}
|
||||||
|
<template #one="one">
|
||||||
|
{{ one }}
|
||||||
|
</template>
|
||||||
|
</foo>
|
||||||
|
`,
|
||||||
|
components: { Foo }
|
||||||
|
}).$mount()
|
||||||
|
expect(`default slot should also use <template>`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shorthand without scope variable', () => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `
|
||||||
|
<foo>
|
||||||
|
<template #one>one</template>
|
||||||
|
<template #two>two</template>
|
||||||
|
</foo>
|
||||||
|
`,
|
||||||
|
components: { Foo }
|
||||||
|
}).$mount()
|
||||||
|
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`onetwo`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shorthand named slots on root', () => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `
|
||||||
|
<foo #one="one">
|
||||||
|
{{ one }}
|
||||||
|
</foo>
|
||||||
|
`,
|
||||||
|
components: { Foo }
|
||||||
|
}).$mount()
|
||||||
|
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dynamic slot name', done => {
|
||||||
|
const vm = new Vue({
|
||||||
|
data: {
|
||||||
|
a: 'one',
|
||||||
|
b: 'two'
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<foo>
|
||||||
|
<template #[a]="one">a {{ one }} </template>
|
||||||
|
<template v-slot:[b]="two">b {{ two }} </template>
|
||||||
|
</foo>
|
||||||
|
`,
|
||||||
|
components: { Foo }
|
||||||
|
}).$mount()
|
||||||
|
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`a from foo one b from foo two`)
|
||||||
|
vm.a = 'two'
|
||||||
|
vm.b = 'one'
|
||||||
|
waitForUpdate(() => {
|
||||||
|
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`b from foo one a from foo two `)
|
||||||
|
}).then(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2.6 scoped slot perf optimization
|
||||||
|
it('should have accurate tracking for scoped slots', done => {
|
||||||
|
const parentUpdate = jasmine.createSpy()
|
||||||
|
const childUpdate = jasmine.createSpy()
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `
|
||||||
|
<div>{{ parentCount }}<foo #default>{{ childCount }}</foo></div>
|
||||||
|
`,
|
||||||
|
data: {
|
||||||
|
parentCount: 0,
|
||||||
|
childCount: 0
|
||||||
|
},
|
||||||
|
updated: parentUpdate,
|
||||||
|
components: {
|
||||||
|
foo: {
|
||||||
|
template: `<div><slot/></div>`,
|
||||||
|
updated: childUpdate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
expect(vm.$el.innerHTML).toMatch(`0<div>0</div>`)
|
||||||
|
|
||||||
|
vm.parentCount++
|
||||||
|
waitForUpdate(() => {
|
||||||
|
expect(vm.$el.innerHTML).toMatch(`1<div>0</div>`)
|
||||||
|
// should only trigger parent update
|
||||||
|
expect(parentUpdate.calls.count()).toBe(1)
|
||||||
|
expect(childUpdate.calls.count()).toBe(0)
|
||||||
|
|
||||||
|
vm.childCount++
|
||||||
|
}).then(() => {
|
||||||
|
expect(vm.$el.innerHTML).toMatch(`1<div>1</div>`)
|
||||||
|
// should only trigger child update
|
||||||
|
expect(parentUpdate.calls.count()).toBe(1)
|
||||||
|
expect(childUpdate.calls.count()).toBe(1)
|
||||||
|
}).then(done)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -229,6 +229,13 @@ describe('codegen', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('generate dynamic scoped slot', () => {
|
||||||
|
assertCodegen(
|
||||||
|
'<foo><template :slot="foo" slot-scope="bar">{{ bar }}</template></foo>',
|
||||||
|
`with(this){return _c('foo',{scopedSlots:_u([{key:foo,fn:function(bar){return [_v(_s(bar))]}}],true)})}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it('generate scoped slot with multiline v-if', () => {
|
it('generate scoped slot with multiline v-if', () => {
|
||||||
assertCodegen(
|
assertCodegen(
|
||||||
'<foo><template v-if="\nshow\n" slot-scope="bar">{{ bar }}</template></foo>',
|
'<foo><template v-if="\nshow\n" slot-scope="bar">{{ bar }}</template></foo>',
|
||||||
|
|
Loading…
Reference in New Issue