feat: v-memo
This commit is contained in:
parent
5cea9a1d4e
commit
3b64508e3b
@ -218,7 +218,7 @@ export function render(_ctx, _cache) {
|
|||||||
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
_createElementVNode(\\"div\\", null, [
|
_createElementVNode(\\"div\\", null, [
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.foo && _ctx.foo(...args)))
|
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.foo && _ctx.foo(...args)))
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
]))
|
]))
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform on component 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
|
_withMemo([_ctx.x], () => _createVNode(_component_Comp), _cache, 0)
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform on normal element 1`] = `
|
||||||
|
"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from \\"vue\\"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
|
_withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\")), _cache, 0)
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform on root element 1`] = `
|
||||||
|
"import { openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo } from \\"vue\\"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\")), _cache, 0)
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform on template v-for 1`] = `
|
||||||
|
"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from \\"vue\\"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
|
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
|
||||||
|
const _memo = ([x, y === z])
|
||||||
|
if (_cached && _cached.key === x && _isMemoSame(_cached.memo, _memo)) return _cached
|
||||||
|
const _item = (_openBlock(), _createElementBlock(\\"span\\", { key: x }, \\"foobar\\"))
|
||||||
|
_item.memo = _memo
|
||||||
|
return _item
|
||||||
|
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform on v-for 1`] = `
|
||||||
|
"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, isMemoSame as _isMemoSame, withMemo as _withMemo } from \\"vue\\"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
|
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
|
||||||
|
const _memo = ([x, y === _ctx.z])
|
||||||
|
if (_cached && _cached.key === x && _isMemoSame(_cached.memo, _memo)) return _cached
|
||||||
|
const _item = (_openBlock(), _createElementBlock(\\"div\\", { key: x }, [
|
||||||
|
_createElementVNode(\\"span\\", null, \\"foobar\\")
|
||||||
|
]))
|
||||||
|
_item.memo = _memo
|
||||||
|
return _item
|
||||||
|
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform on v-if 1`] = `
|
||||||
|
"import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
|
(_ctx.ok)
|
||||||
|
? _withMemo([_ctx.x], () => (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, [
|
||||||
|
_createElementVNode(\\"span\\", null, \\"foo\\"),
|
||||||
|
_createTextVNode(\\"bar\\")
|
||||||
|
])), _cache, 0)
|
||||||
|
: _withMemo([_ctx.x], () => (_openBlock(), _createBlock(_component_Comp, { key: 1 })), _cache, 1)
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
@ -7,11 +7,11 @@ return function render(_ctx, _cache) {
|
|||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
||||||
return _cache[1] || (
|
return _cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
_cache[1] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
_cache[0] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[1]
|
_cache[0]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -27,11 +27,11 @@ return function render(_ctx, _cache) {
|
|||||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
_cache[1] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
_cache[1] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[1]
|
_cache[0]
|
||||||
)
|
)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
@ -46,11 +46,11 @@ return function render(_ctx, _cache) {
|
|||||||
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
_cache[1] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
_cache[1] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
_cache[0] = _createElementVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[1]
|
_cache[0]
|
||||||
)
|
)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
@ -65,11 +65,11 @@ return function render(_ctx, _cache) {
|
|||||||
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
_cache[1] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
_cache[1] = _renderSlot($slots, \\"default\\"),
|
_cache[0] = _renderSlot($slots, \\"default\\"),
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[1]
|
_cache[0]
|
||||||
)
|
)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
@ -84,11 +84,11 @@ return function render(_ctx, _cache) {
|
|||||||
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
return (_openBlock(), _createElementBlock(\\"div\\", null, [
|
||||||
_cache[1] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
_cache[1] = _createElementVNode(\\"div\\"),
|
_cache[0] = _createElementVNode(\\"div\\"),
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[1]
|
_cache[0]
|
||||||
)
|
)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
56
packages/compiler-core/__tests__/transforms/vMemo.spec.ts
Normal file
56
packages/compiler-core/__tests__/transforms/vMemo.spec.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { baseCompile } from '../../src'
|
||||||
|
|
||||||
|
describe('compiler: v-memo transform', () => {
|
||||||
|
function compile(content: string) {
|
||||||
|
return baseCompile(`<div>${content}</div>`, {
|
||||||
|
mode: 'module',
|
||||||
|
prefixIdentifiers: true
|
||||||
|
}).code
|
||||||
|
}
|
||||||
|
|
||||||
|
test('on root element', () => {
|
||||||
|
expect(
|
||||||
|
baseCompile(`<div v-memo="[x]"></div>`, {
|
||||||
|
mode: 'module',
|
||||||
|
prefixIdentifiers: true
|
||||||
|
}).code
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on normal element', () => {
|
||||||
|
expect(compile(`<div v-memo="[x]"></div>`)).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on component', () => {
|
||||||
|
expect(compile(`<Comp v-memo="[x]"></Comp>`)).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on v-if', () => {
|
||||||
|
expect(
|
||||||
|
compile(
|
||||||
|
`<div v-if="ok" v-memo="[x]"><span>foo</span>bar</div>
|
||||||
|
<Comp v-else v-memo="[x]"></Comp>`
|
||||||
|
)
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on v-for', () => {
|
||||||
|
expect(
|
||||||
|
compile(
|
||||||
|
`<div v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
|
||||||
|
<span>foobar</span>
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on template v-for', () => {
|
||||||
|
expect(
|
||||||
|
compile(
|
||||||
|
`<template v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
|
||||||
|
<span>foobar</span>
|
||||||
|
</template>`
|
||||||
|
)
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
@ -452,7 +452,7 @@ describe('compiler: transform v-on', () => {
|
|||||||
(vnodeCall.props as ObjectExpression).properties[0].value
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `() => {}`
|
content: `() => {}`
|
||||||
@ -473,7 +473,7 @@ describe('compiler: transform v-on', () => {
|
|||||||
(vnodeCall.props as ObjectExpression).properties[0].value
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [
|
children: [
|
||||||
@ -498,7 +498,7 @@ describe('compiler: transform v-on', () => {
|
|||||||
(vnodeCall.props as ObjectExpression).properties[0].value
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [
|
children: [
|
||||||
@ -543,7 +543,7 @@ describe('compiler: transform v-on', () => {
|
|||||||
(vnodeCall.props as ObjectExpression).properties[0].value
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [`() => `, { content: `_ctx.foo` }, `()`]
|
children: [`() => `, { content: `_ctx.foo` }, `()`]
|
||||||
@ -565,7 +565,7 @@ describe('compiler: transform v-on', () => {
|
|||||||
(vnodeCall.props as ObjectExpression).properties[0].value
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [
|
children: [
|
||||||
|
@ -26,7 +26,7 @@ describe('compiler: v-once transform', () => {
|
|||||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||||
expect(root.codegenNode).toMatchObject({
|
expect(root.codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.VNODE_CALL,
|
type: NodeTypes.VNODE_CALL,
|
||||||
tag: `"div"`
|
tag: `"div"`
|
||||||
@ -41,7 +41,7 @@ describe('compiler: v-once transform', () => {
|
|||||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.VNODE_CALL,
|
type: NodeTypes.VNODE_CALL,
|
||||||
tag: `"div"`
|
tag: `"div"`
|
||||||
@ -56,7 +56,7 @@ describe('compiler: v-once transform', () => {
|
|||||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.VNODE_CALL,
|
type: NodeTypes.VNODE_CALL,
|
||||||
tag: `_component_Comp`
|
tag: `_component_Comp`
|
||||||
@ -71,7 +71,7 @@ describe('compiler: v-once transform', () => {
|
|||||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: RENDER_SLOT
|
callee: RENDER_SLOT
|
||||||
@ -90,7 +90,7 @@ describe('compiler: v-once transform', () => {
|
|||||||
expect(root.hoists.length).toBe(0)
|
expect(root.hoists.length).toBe(0)
|
||||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.VNODE_CALL,
|
type: NodeTypes.VNODE_CALL,
|
||||||
tag: `"div"`
|
tag: `"div"`
|
||||||
|
@ -6,7 +6,8 @@ import {
|
|||||||
RENDER_LIST,
|
RENDER_LIST,
|
||||||
OPEN_BLOCK,
|
OPEN_BLOCK,
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
WITH_DIRECTIVES
|
WITH_DIRECTIVES,
|
||||||
|
WITH_MEMO
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { PropsExpression } from './transforms/transformElement'
|
import { PropsExpression } from './transforms/transformElement'
|
||||||
import { ImportItem, TransformContext } from './transform'
|
import { ImportItem, TransformContext } from './transform'
|
||||||
@ -135,6 +136,7 @@ export interface PlainElementNode extends BaseElementNode {
|
|||||||
| VNodeCall
|
| VNodeCall
|
||||||
| SimpleExpressionNode // when hoisted
|
| SimpleExpressionNode // when hoisted
|
||||||
| CacheExpression // when cached by v-once
|
| CacheExpression // when cached by v-once
|
||||||
|
| MemoExpression // when cached by v-memo
|
||||||
| undefined
|
| undefined
|
||||||
ssrCodegenNode?: TemplateLiteral
|
ssrCodegenNode?: TemplateLiteral
|
||||||
}
|
}
|
||||||
@ -144,6 +146,7 @@ export interface ComponentNode extends BaseElementNode {
|
|||||||
codegenNode:
|
codegenNode:
|
||||||
| VNodeCall
|
| VNodeCall
|
||||||
| CacheExpression // when cached by v-once
|
| CacheExpression // when cached by v-once
|
||||||
|
| MemoExpression // when cached by v-memo
|
||||||
| undefined
|
| undefined
|
||||||
ssrCodegenNode?: CallExpression
|
ssrCodegenNode?: CallExpression
|
||||||
}
|
}
|
||||||
@ -375,6 +378,15 @@ export interface CacheExpression extends Node {
|
|||||||
isVNode: boolean
|
isVNode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MemoExpression extends CallExpression {
|
||||||
|
callee: typeof WITH_MEMO
|
||||||
|
arguments: [ExpressionNode, MemoFactory, string, string]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MemoFactory extends FunctionExpression {
|
||||||
|
returns: BlockCodegenNode
|
||||||
|
}
|
||||||
|
|
||||||
// SSR-specific Node Types -----------------------------------------------------
|
// SSR-specific Node Types -----------------------------------------------------
|
||||||
|
|
||||||
export type SSRCodegenNode =
|
export type SSRCodegenNode =
|
||||||
@ -499,8 +511,8 @@ export interface DynamicSlotFnProperty extends Property {
|
|||||||
export type BlockCodegenNode = VNodeCall | RenderSlotCall
|
export type BlockCodegenNode = VNodeCall | RenderSlotCall
|
||||||
|
|
||||||
export interface IfConditionalExpression extends ConditionalExpression {
|
export interface IfConditionalExpression extends ConditionalExpression {
|
||||||
consequent: BlockCodegenNode
|
consequent: BlockCodegenNode | MemoExpression
|
||||||
alternate: BlockCodegenNode | IfConditionalExpression
|
alternate: BlockCodegenNode | IfConditionalExpression | MemoExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForCodegenNode extends VNodeCall {
|
export interface ForCodegenNode extends VNodeCall {
|
||||||
@ -627,7 +639,7 @@ export function createObjectProperty(
|
|||||||
|
|
||||||
export function createSimpleExpression(
|
export function createSimpleExpression(
|
||||||
content: SimpleExpressionNode['content'],
|
content: SimpleExpressionNode['content'],
|
||||||
isStatic: SimpleExpressionNode['isStatic'],
|
isStatic: SimpleExpressionNode['isStatic'] = false,
|
||||||
loc: SourceLocation = locStub,
|
loc: SourceLocation = locStub,
|
||||||
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
|
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
|
||||||
): SimpleExpressionNode {
|
): SimpleExpressionNode {
|
||||||
|
@ -651,11 +651,11 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
|||||||
case NodeTypes.JS_CACHE_EXPRESSION:
|
case NodeTypes.JS_CACHE_EXPRESSION:
|
||||||
genCacheExpression(node, context)
|
genCacheExpression(node, context)
|
||||||
break
|
break
|
||||||
|
case NodeTypes.JS_BLOCK_STATEMENT:
|
||||||
|
genNodeList(node.body, context, true, false)
|
||||||
|
break
|
||||||
|
|
||||||
// SSR only types
|
// SSR only types
|
||||||
case NodeTypes.JS_BLOCK_STATEMENT:
|
|
||||||
!__BROWSER__ && genNodeList(node.body, context, true, false)
|
|
||||||
break
|
|
||||||
case NodeTypes.JS_TEMPLATE_LITERAL:
|
case NodeTypes.JS_TEMPLATE_LITERAL:
|
||||||
!__BROWSER__ && genTemplateLiteral(node, context)
|
!__BROWSER__ && genTemplateLiteral(node, context)
|
||||||
break
|
break
|
||||||
|
@ -17,6 +17,7 @@ import { transformOnce } from './transforms/vOnce'
|
|||||||
import { transformModel } from './transforms/vModel'
|
import { transformModel } from './transforms/vModel'
|
||||||
import { transformFilter } from './compat/transformFilter'
|
import { transformFilter } from './compat/transformFilter'
|
||||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||||
|
import { transformMemo } from './transforms/vMemo'
|
||||||
|
|
||||||
export type TransformPreset = [
|
export type TransformPreset = [
|
||||||
NodeTransform[],
|
NodeTransform[],
|
||||||
@ -30,6 +31,7 @@ export function getBaseTransformPreset(
|
|||||||
[
|
[
|
||||||
transformOnce,
|
transformOnce,
|
||||||
transformIf,
|
transformIf,
|
||||||
|
transformMemo,
|
||||||
transformFor,
|
transformFor,
|
||||||
...(__COMPAT__ ? [transformFilter] : []),
|
...(__COMPAT__ ? [transformFilter] : []),
|
||||||
...(!__BROWSER__ && prefixIdentifiers
|
...(!__BROWSER__ && prefixIdentifiers
|
||||||
|
@ -38,6 +38,8 @@ export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
|
|||||||
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
|
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
|
||||||
export const UNREF = Symbol(__DEV__ ? `unref` : ``)
|
export const UNREF = Symbol(__DEV__ ? `unref` : ``)
|
||||||
export const IS_REF = Symbol(__DEV__ ? `isRef` : ``)
|
export const IS_REF = Symbol(__DEV__ ? `isRef` : ``)
|
||||||
|
export const WITH_MEMO = Symbol(__DEV__ ? `withMemo` : ``)
|
||||||
|
export const IS_MEMO_SAME = Symbol(__DEV__ ? `isMemoSame` : ``)
|
||||||
|
|
||||||
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
||||||
// generated code. Make sure these are correctly exported in the runtime!
|
// generated code. Make sure these are correctly exported in the runtime!
|
||||||
@ -80,7 +82,9 @@ export const helperNameMap: any = {
|
|||||||
[WITH_SCOPE_ID]: `withScopeId`,
|
[WITH_SCOPE_ID]: `withScopeId`,
|
||||||
[WITH_CTX]: `withCtx`,
|
[WITH_CTX]: `withCtx`,
|
||||||
[UNREF]: `unref`,
|
[UNREF]: `unref`,
|
||||||
[IS_REF]: `isRef`
|
[IS_REF]: `isRef`,
|
||||||
|
[WITH_MEMO]: `withMemo`,
|
||||||
|
[IS_MEMO_SAME]: `isMemoSame`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerRuntimeHelpers(helpers: any) {
|
export function registerRuntimeHelpers(helpers: any) {
|
||||||
|
@ -34,10 +34,9 @@ import {
|
|||||||
TO_DISPLAY_STRING,
|
TO_DISPLAY_STRING,
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
helperNameMap,
|
helperNameMap,
|
||||||
CREATE_COMMENT,
|
CREATE_COMMENT
|
||||||
OPEN_BLOCK
|
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { getVNodeBlockHelper, getVNodeHelper, isVSlot } from './utils'
|
import { isVSlot, makeBlock } from './utils'
|
||||||
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
||||||
import { CompilerCompatOptions } from './compat/compatConfig'
|
import { CompilerCompatOptions } from './compat/compatConfig'
|
||||||
|
|
||||||
@ -278,7 +277,7 @@ export function createTransformContext(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
hoist(exp) {
|
hoist(exp) {
|
||||||
if (isString(exp)) exp = createSimpleExpression(exp, false)
|
if (isString(exp)) exp = createSimpleExpression(exp)
|
||||||
context.hoists.push(exp)
|
context.hoists.push(exp)
|
||||||
const identifier = createSimpleExpression(
|
const identifier = createSimpleExpression(
|
||||||
`_hoisted_${context.hoists.length}`,
|
`_hoisted_${context.hoists.length}`,
|
||||||
@ -290,7 +289,7 @@ export function createTransformContext(
|
|||||||
return identifier
|
return identifier
|
||||||
},
|
},
|
||||||
cache(exp, isVNode = false) {
|
cache(exp, isVNode = false) {
|
||||||
return createCacheExpression(++context.cached, exp, isVNode)
|
return createCacheExpression(context.cached++, exp, isVNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,7 +336,7 @@ export function transform(root: RootNode, options: TransformOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createRootCodegen(root: RootNode, context: TransformContext) {
|
function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||||
const { helper, removeHelper } = context
|
const { helper } = context
|
||||||
const { children } = root
|
const { children } = root
|
||||||
if (children.length === 1) {
|
if (children.length === 1) {
|
||||||
const child = children[0]
|
const child = children[0]
|
||||||
@ -347,12 +346,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||||||
// SimpleExpressionNode
|
// SimpleExpressionNode
|
||||||
const codegenNode = child.codegenNode
|
const codegenNode = child.codegenNode
|
||||||
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||||
if (!codegenNode.isBlock) {
|
makeBlock(codegenNode, context)
|
||||||
codegenNode.isBlock = true
|
|
||||||
removeHelper(getVNodeHelper(context.inSSR, codegenNode.isComponent))
|
|
||||||
helper(OPEN_BLOCK)
|
|
||||||
helper(getVNodeBlockHelper(context.inSSR, codegenNode.isComponent))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
root.codegenNode = codegenNode
|
root.codegenNode = codegenNode
|
||||||
} else {
|
} else {
|
||||||
|
@ -504,8 +504,8 @@ export function buildProps(
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// skip v-once - it is handled by its dedicated transform.
|
// skip v-once/v-memo - they are handled by dedicated transforms.
|
||||||
if (name === 'once') {
|
if (name === 'once' || name === 'memo') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// skip v-is and :is on <component>
|
// skip v-is and :is on <component>
|
||||||
|
@ -24,7 +24,9 @@ import {
|
|||||||
ForRenderListExpression,
|
ForRenderListExpression,
|
||||||
BlockCodegenNode,
|
BlockCodegenNode,
|
||||||
ForIteratorExpression,
|
ForIteratorExpression,
|
||||||
ConstantTypes
|
ConstantTypes,
|
||||||
|
createBlockStatement,
|
||||||
|
createCompoundExpression
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
@ -34,9 +36,15 @@ import {
|
|||||||
isSlotOutlet,
|
isSlotOutlet,
|
||||||
injectProp,
|
injectProp,
|
||||||
getVNodeBlockHelper,
|
getVNodeBlockHelper,
|
||||||
getVNodeHelper
|
getVNodeHelper,
|
||||||
|
findDir
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { RENDER_LIST, OPEN_BLOCK, FRAGMENT } from '../runtimeHelpers'
|
import {
|
||||||
|
RENDER_LIST,
|
||||||
|
OPEN_BLOCK,
|
||||||
|
FRAGMENT,
|
||||||
|
IS_MEMO_SAME
|
||||||
|
} from '../runtimeHelpers'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
import { validateBrowserExpression } from '../validateExpression'
|
import { validateBrowserExpression } from '../validateExpression'
|
||||||
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||||
@ -51,15 +59,14 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
const renderExp = createCallExpression(helper(RENDER_LIST), [
|
const renderExp = createCallExpression(helper(RENDER_LIST), [
|
||||||
forNode.source
|
forNode.source
|
||||||
]) as ForRenderListExpression
|
]) as ForRenderListExpression
|
||||||
|
const memo = findDir(node, 'memo')
|
||||||
const keyProp = findProp(node, `key`)
|
const keyProp = findProp(node, `key`)
|
||||||
const keyProperty = keyProp
|
const keyExp =
|
||||||
? createObjectProperty(
|
keyProp &&
|
||||||
`key`,
|
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||||
keyProp.type === NodeTypes.ATTRIBUTE
|
? createSimpleExpression(keyProp.value!.content, true)
|
||||||
? createSimpleExpression(keyProp.value!.content, true)
|
: keyProp.exp!)
|
||||||
: keyProp.exp!
|
const keyProperty = keyProp ? createObjectProperty(`key`, keyExp!) : null
|
||||||
)
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers && keyProperty) {
|
if (!__BROWSER__ && context.prefixIdentifiers && keyProperty) {
|
||||||
// #2085 process :key expression needs to be processed in order for it
|
// #2085 process :key expression needs to be processed in order for it
|
||||||
@ -189,11 +196,37 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderExp.arguments.push(createFunctionExpression(
|
if (memo) {
|
||||||
createForLoopParams(forNode.parseResult),
|
const loop = createFunctionExpression(
|
||||||
childBlock,
|
createForLoopParams(forNode.parseResult, [
|
||||||
true /* force newline */
|
createSimpleExpression(`_cached`)
|
||||||
) as ForIteratorExpression)
|
])
|
||||||
|
)
|
||||||
|
loop.body = createBlockStatement([
|
||||||
|
createCompoundExpression([`const _memo = (`, memo.exp!, `)`]),
|
||||||
|
createCompoundExpression([
|
||||||
|
`if (_cached`,
|
||||||
|
...(keyExp ? [` && _cached.key === `, keyExp] : []),
|
||||||
|
` && ${context.helperString(
|
||||||
|
IS_MEMO_SAME
|
||||||
|
)}(_cached.memo, _memo)) return _cached`
|
||||||
|
]),
|
||||||
|
createCompoundExpression([`const _item = `, childBlock as any]),
|
||||||
|
createSimpleExpression(`_item.memo = _memo`),
|
||||||
|
createSimpleExpression(`return _item`)
|
||||||
|
])
|
||||||
|
renderExp.arguments.push(
|
||||||
|
loop as ForIteratorExpression,
|
||||||
|
createSimpleExpression(`_cache`),
|
||||||
|
createSimpleExpression(String(context.cached++))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
renderExp.arguments.push(createFunctionExpression(
|
||||||
|
createForLoopParams(forNode.parseResult),
|
||||||
|
childBlock,
|
||||||
|
true /* force newline */
|
||||||
|
) as ForIteratorExpression)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -393,29 +426,21 @@ function createAliasExpression(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createForLoopParams({
|
export function createForLoopParams(
|
||||||
value,
|
{ value, key, index }: ForParseResult,
|
||||||
key,
|
memoArgs: ExpressionNode[] = []
|
||||||
index
|
): ExpressionNode[] {
|
||||||
}: ForParseResult): ExpressionNode[] {
|
return createParamsList([value, key, index, ...memoArgs])
|
||||||
const params: ExpressionNode[] = []
|
}
|
||||||
if (value) {
|
|
||||||
params.push(value)
|
function createParamsList(
|
||||||
}
|
args: (ExpressionNode | undefined)[]
|
||||||
if (key) {
|
): ExpressionNode[] {
|
||||||
if (!value) {
|
let i = args.length
|
||||||
params.push(createSimpleExpression(`_`, false))
|
while (i--) {
|
||||||
}
|
if (args[i]) break
|
||||||
params.push(key)
|
}
|
||||||
}
|
return args
|
||||||
if (index) {
|
.slice(0, i + 1)
|
||||||
if (!key) {
|
.map((arg, i) => arg || createSimpleExpression(`_`.repeat(i + 1), false))
|
||||||
if (!value) {
|
|
||||||
params.push(createSimpleExpression(`_`, false))
|
|
||||||
}
|
|
||||||
params.push(createSimpleExpression(`__`, false))
|
|
||||||
}
|
|
||||||
params.push(index)
|
|
||||||
}
|
|
||||||
return params
|
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,22 @@ import {
|
|||||||
AttributeNode,
|
AttributeNode,
|
||||||
locStub,
|
locStub,
|
||||||
CacheExpression,
|
CacheExpression,
|
||||||
ConstantTypes
|
ConstantTypes,
|
||||||
|
MemoExpression
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
import { validateBrowserExpression } from '../validateExpression'
|
import { validateBrowserExpression } from '../validateExpression'
|
||||||
import { FRAGMENT, CREATE_COMMENT, OPEN_BLOCK } from '../runtimeHelpers'
|
import { FRAGMENT, CREATE_COMMENT } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
injectProp,
|
injectProp,
|
||||||
findDir,
|
findDir,
|
||||||
findProp,
|
findProp,
|
||||||
isBuiltInType,
|
isBuiltInType,
|
||||||
getVNodeHelper,
|
makeBlock
|
||||||
getVNodeBlockHelper
|
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||||
|
import { getMemoedVNodeCall } from '..'
|
||||||
|
|
||||||
export const transformIf = createStructuralDirectiveTransform(
|
export const transformIf = createStructuralDirectiveTransform(
|
||||||
/^(if|else|else-if)$/,
|
/^(if|else|else-if)$/,
|
||||||
@ -214,7 +215,7 @@ function createCodegenNodeForBranch(
|
|||||||
branch: IfBranchNode,
|
branch: IfBranchNode,
|
||||||
keyIndex: number,
|
keyIndex: number,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): IfConditionalExpression | BlockCodegenNode {
|
): IfConditionalExpression | BlockCodegenNode | MemoExpression {
|
||||||
if (branch.condition) {
|
if (branch.condition) {
|
||||||
return createConditionalExpression(
|
return createConditionalExpression(
|
||||||
branch.condition,
|
branch.condition,
|
||||||
@ -235,8 +236,8 @@ function createChildrenCodegenNode(
|
|||||||
branch: IfBranchNode,
|
branch: IfBranchNode,
|
||||||
keyIndex: number,
|
keyIndex: number,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): BlockCodegenNode {
|
): BlockCodegenNode | MemoExpression {
|
||||||
const { helper, removeHelper } = context
|
const { helper } = context
|
||||||
const keyProperty = createObjectProperty(
|
const keyProperty = createObjectProperty(
|
||||||
`key`,
|
`key`,
|
||||||
createSimpleExpression(
|
createSimpleExpression(
|
||||||
@ -284,18 +285,17 @@ function createChildrenCodegenNode(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const vnodeCall = (firstChild as ElementNode)
|
const ret = (firstChild as ElementNode).codegenNode as
|
||||||
.codegenNode as BlockCodegenNode
|
| BlockCodegenNode
|
||||||
|
| MemoExpression
|
||||||
|
const vnodeCall = getMemoedVNodeCall(ret)
|
||||||
// Change createVNode to createBlock.
|
// Change createVNode to createBlock.
|
||||||
if (vnodeCall.type === NodeTypes.VNODE_CALL && !vnodeCall.isBlock) {
|
if (vnodeCall.type === NodeTypes.VNODE_CALL) {
|
||||||
removeHelper(getVNodeHelper(context.inSSR, vnodeCall.isComponent))
|
makeBlock(vnodeCall, context)
|
||||||
vnodeCall.isBlock = true
|
|
||||||
helper(OPEN_BLOCK)
|
|
||||||
helper(getVNodeBlockHelper(context.inSSR, vnodeCall.isComponent))
|
|
||||||
}
|
}
|
||||||
// inject branch key
|
// inject branch key
|
||||||
injectProp(vnodeCall, keyProperty, context)
|
injectProp(vnodeCall, keyProperty, context)
|
||||||
return vnodeCall
|
return ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
40
packages/compiler-core/src/transforms/vMemo.ts
Normal file
40
packages/compiler-core/src/transforms/vMemo.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { NodeTransform } from '../transform'
|
||||||
|
import { findDir, makeBlock } from '../utils'
|
||||||
|
import {
|
||||||
|
createCallExpression,
|
||||||
|
createFunctionExpression,
|
||||||
|
ElementTypes,
|
||||||
|
MemoExpression,
|
||||||
|
NodeTypes,
|
||||||
|
PlainElementNode
|
||||||
|
} from '../ast'
|
||||||
|
import { WITH_MEMO } from '../runtimeHelpers'
|
||||||
|
|
||||||
|
const seen = new WeakSet()
|
||||||
|
|
||||||
|
export const transformMemo: NodeTransform = (node, context) => {
|
||||||
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
|
const dir = findDir(node, 'memo')
|
||||||
|
if (!dir || seen.has(node)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen.add(node)
|
||||||
|
return () => {
|
||||||
|
const codegenNode =
|
||||||
|
node.codegenNode ||
|
||||||
|
(context.currentNode as PlainElementNode).codegenNode
|
||||||
|
if (codegenNode && codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||||
|
// non-component sub tree should be turned into a block
|
||||||
|
if (node.tagType !== ElementTypes.COMPONENT) {
|
||||||
|
makeBlock(codegenNode, context)
|
||||||
|
}
|
||||||
|
node.codegenNode = createCallExpression(context.helper(WITH_MEMO), [
|
||||||
|
dir.exp!,
|
||||||
|
createFunctionExpression(undefined, codegenNode),
|
||||||
|
`_cache`,
|
||||||
|
String(context.cached++)
|
||||||
|
]) as MemoExpression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,9 @@ import {
|
|||||||
TextNode,
|
TextNode,
|
||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
VNodeCall,
|
VNodeCall,
|
||||||
SimpleExpressionNode
|
SimpleExpressionNode,
|
||||||
|
BlockCodegenNode,
|
||||||
|
MemoExpression
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { TransformContext } from './transform'
|
import { TransformContext } from './transform'
|
||||||
import {
|
import {
|
||||||
@ -36,7 +38,9 @@ import {
|
|||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
CREATE_ELEMENT_BLOCK,
|
CREATE_ELEMENT_BLOCK,
|
||||||
CREATE_VNODE,
|
CREATE_VNODE,
|
||||||
CREATE_ELEMENT_VNODE
|
CREATE_ELEMENT_VNODE,
|
||||||
|
WITH_MEMO,
|
||||||
|
OPEN_BLOCK
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { isString, isObject, hyphenate, extend } from '@vue/shared'
|
import { isString, isObject, hyphenate, extend } from '@vue/shared'
|
||||||
import { PropsExpression } from './transforms/transformElement'
|
import { PropsExpression } from './transforms/transformElement'
|
||||||
@ -483,3 +487,23 @@ export function hasScopeRef(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
|
||||||
|
if (node.type === NodeTypes.JS_CALL_EXPRESSION && node.callee === WITH_MEMO) {
|
||||||
|
return node.arguments[1].returns as VNodeCall
|
||||||
|
} else {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeBlock(
|
||||||
|
node: VNodeCall,
|
||||||
|
{ helper, removeHelper, inSSR }: TransformContext
|
||||||
|
) {
|
||||||
|
if (!node.isBlock) {
|
||||||
|
node.isBlock = true
|
||||||
|
removeHelper(getVNodeHelper(inSSR, node.isComponent))
|
||||||
|
helper(OPEN_BLOCK)
|
||||||
|
helper(getVNodeBlockHelper(inSSR, node.isComponent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -278,7 +278,7 @@ describe('compiler-dom: transform v-on', () => {
|
|||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 0,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: V_ON_WITH_KEYS
|
callee: V_ON_WITH_KEYS
|
||||||
|
@ -414,19 +414,19 @@ export default {
|
|||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[1] || (_cache[1] = $event => (count.value = 1))
|
onClick: _cache[0] || (_cache[0] = $event => (count.value = 1))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[2] || (_cache[2] = $event => (maybe.value = count.value))
|
onClick: _cache[1] || (_cache[1] = $event => (maybe.value = count.value))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[3] || (_cache[3] = $event => (_isRef(lett) ? lett.value = count.value : lett = count.value))
|
onClick: _cache[2] || (_cache[2] = $event => (_isRef(lett) ? lett.value = count.value : lett = count.value))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[4] || (_cache[4] = $event => (_isRef(v) ? v.value += 1 : v += 1))
|
onClick: _cache[3] || (_cache[3] = $event => (_isRef(v) ? v.value += 1 : v += 1))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[5] || (_cache[5] = $event => (_isRef(v) ? v.value -= 1 : v -= 1))
|
onClick: _cache[4] || (_cache[4] = $event => (_isRef(v) ? v.value -= 1 : v -= 1))
|
||||||
})
|
})
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -451,13 +451,13 @@ export default {
|
|||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[1] || (_cache[1] = $event => (({ count: count.value } = val)))
|
onClick: _cache[0] || (_cache[0] = $event => (({ count: count.value } = val)))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[2] || (_cache[2] = $event => ([maybe.value] = val))
|
onClick: _cache[1] || (_cache[1] = $event => ([maybe.value] = val))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[3] || (_cache[3] = $event => (({ lett: lett } = val)))
|
onClick: _cache[2] || (_cache[2] = $event => (({ lett: lett } = val)))
|
||||||
})
|
})
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -481,22 +481,22 @@ export default {
|
|||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[1] || (_cache[1] = $event => (count.value++))
|
onClick: _cache[0] || (_cache[0] = $event => (count.value++))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[2] || (_cache[2] = $event => (--count.value))
|
onClick: _cache[1] || (_cache[1] = $event => (--count.value))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[3] || (_cache[3] = $event => (maybe.value++))
|
onClick: _cache[2] || (_cache[2] = $event => (maybe.value++))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[4] || (_cache[4] = $event => (--maybe.value))
|
onClick: _cache[3] || (_cache[3] = $event => (--maybe.value))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[5] || (_cache[5] = $event => (_isRef(lett) ? lett.value++ : lett++))
|
onClick: _cache[4] || (_cache[4] = $event => (_isRef(lett) ? lett.value++ : lett++))
|
||||||
}),
|
}),
|
||||||
_createElementVNode(\\"div\\", {
|
_createElementVNode(\\"div\\", {
|
||||||
onClick: _cache[6] || (_cache[6] = $event => (_isRef(lett) ? --lett.value : --lett))
|
onClick: _cache[5] || (_cache[5] = $event => (_isRef(lett) ? --lett.value : --lett))
|
||||||
})
|
})
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -520,17 +520,17 @@ export default {
|
|||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
_withDirectives(_createElementVNode(\\"input\\", {
|
_withDirectives(_createElementVNode(\\"input\\", {
|
||||||
\\"onUpdate:modelValue\\": _cache[1] || (_cache[1] = $event => (count.value = $event))
|
\\"onUpdate:modelValue\\": _cache[0] || (_cache[0] = $event => (count.value = $event))
|
||||||
}, null, 512 /* NEED_PATCH */), [
|
}, null, 512 /* NEED_PATCH */), [
|
||||||
[_vModelText, count.value]
|
[_vModelText, count.value]
|
||||||
]),
|
]),
|
||||||
_withDirectives(_createElementVNode(\\"input\\", {
|
_withDirectives(_createElementVNode(\\"input\\", {
|
||||||
\\"onUpdate:modelValue\\": _cache[2] || (_cache[2] = $event => (_isRef(maybe) ? maybe.value = $event : null))
|
\\"onUpdate:modelValue\\": _cache[1] || (_cache[1] = $event => (_isRef(maybe) ? maybe.value = $event : null))
|
||||||
}, null, 512 /* NEED_PATCH */), [
|
}, null, 512 /* NEED_PATCH */), [
|
||||||
[_vModelText, _unref(maybe)]
|
[_vModelText, _unref(maybe)]
|
||||||
]),
|
]),
|
||||||
_withDirectives(_createElementVNode(\\"input\\", {
|
_withDirectives(_createElementVNode(\\"input\\", {
|
||||||
\\"onUpdate:modelValue\\": _cache[3] || (_cache[3] = $event => (_isRef(lett) ? lett.value = $event : lett = $event))
|
\\"onUpdate:modelValue\\": _cache[2] || (_cache[2] = $event => (_isRef(lett) ? lett.value = $event : lett = $event))
|
||||||
}, null, 512 /* NEED_PATCH */), [
|
}, null, 512 /* NEED_PATCH */), [
|
||||||
[_vModelText, _unref(lett)]
|
[_vModelText, _unref(lett)]
|
||||||
])
|
])
|
||||||
|
150
packages/runtime-core/__tests__/helpers/withMemo.spec.ts
Normal file
150
packages/runtime-core/__tests__/helpers/withMemo.spec.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// since v-memo really is a compiler + runtime combo feature, we are performing
|
||||||
|
// more of an itegration test here.
|
||||||
|
import { ComponentOptions, createApp, nextTick } from 'vue'
|
||||||
|
|
||||||
|
describe('v-memo', () => {
|
||||||
|
function mount(options: ComponentOptions): [HTMLElement, any] {
|
||||||
|
const app = createApp(options)
|
||||||
|
const el = document.createElement('div')
|
||||||
|
const vm = app.mount(el)
|
||||||
|
return [el, vm]
|
||||||
|
}
|
||||||
|
|
||||||
|
test('on normal element', async () => {
|
||||||
|
const [el, vm] = mount({
|
||||||
|
template: `<div v-memo="[x]">{{ x }} {{ y }}</div>`,
|
||||||
|
data: () => ({ x: 0, y: 0 })
|
||||||
|
})
|
||||||
|
expect(el.innerHTML).toBe(`<div>0 0</div>`)
|
||||||
|
|
||||||
|
vm.x++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>1 0</div>`)
|
||||||
|
|
||||||
|
vm.y++
|
||||||
|
// should not update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>1 0</div>`)
|
||||||
|
|
||||||
|
vm.x++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>2 1</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on component', async () => {
|
||||||
|
const [el, vm] = mount({
|
||||||
|
template: `<Comp v-memo="[x]" :x="x" :y="y"></Comp>`,
|
||||||
|
data: () => ({ x: 0, y: 0 }),
|
||||||
|
components: {
|
||||||
|
Comp: {
|
||||||
|
props: ['x', 'y'],
|
||||||
|
template: `<div>{{x}} {{y}}</div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(el.innerHTML).toBe(`<div>0 0</div>`)
|
||||||
|
|
||||||
|
vm.x++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>1 0</div>`)
|
||||||
|
|
||||||
|
vm.y++
|
||||||
|
// should not update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>1 0</div>`)
|
||||||
|
|
||||||
|
vm.x++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>2 1</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on v-if', async () => {
|
||||||
|
const [el, vm] = mount({
|
||||||
|
template: `<div v-if="ok" v-memo="[x]">{{ x }} {{ y }}</div>
|
||||||
|
<div v-else v-memo="[y]">{{ y }} {{ x }}</div>`,
|
||||||
|
data: () => ({ ok: true, x: 0, y: 0 })
|
||||||
|
})
|
||||||
|
expect(el.innerHTML).toBe(`<div>0 0</div>`)
|
||||||
|
|
||||||
|
vm.x++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>1 0</div>`)
|
||||||
|
|
||||||
|
vm.y++
|
||||||
|
// should not update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>1 0</div>`)
|
||||||
|
|
||||||
|
vm.x++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>2 1</div>`)
|
||||||
|
|
||||||
|
vm.ok = false
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>1 2</div>`)
|
||||||
|
|
||||||
|
vm.y++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>2 2</div>`)
|
||||||
|
|
||||||
|
vm.x++
|
||||||
|
// should not update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>2 2</div>`)
|
||||||
|
|
||||||
|
vm.y++
|
||||||
|
// should update
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(`<div>3 3</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on v-for', async () => {
|
||||||
|
const [el, vm] = mount({
|
||||||
|
template:
|
||||||
|
`<div v-for="{ x } in list" :key="x" v-memo="[x, x === y]">` +
|
||||||
|
`{{ x }} {{ x === y ? 'yes' : 'no' }} {{ z }}` +
|
||||||
|
`</div>`,
|
||||||
|
data: () => ({
|
||||||
|
list: [{ x: 1 }, { x: 2 }, { x: 3 }],
|
||||||
|
y: 1,
|
||||||
|
z: 'z'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
expect(el.innerHTML).toBe(
|
||||||
|
`<div>1 yes z</div><div>2 no z</div><div>3 no z</div>`
|
||||||
|
)
|
||||||
|
|
||||||
|
vm.y = 2
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(
|
||||||
|
`<div>1 no z</div><div>2 yes z</div><div>3 no z</div>`
|
||||||
|
)
|
||||||
|
|
||||||
|
vm.list[0].x = 4
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(
|
||||||
|
`<div>4 no z</div><div>2 yes z</div><div>3 no z</div>`
|
||||||
|
)
|
||||||
|
|
||||||
|
vm.list[0].x = 5
|
||||||
|
vm.y = 5
|
||||||
|
await nextTick()
|
||||||
|
expect(el.innerHTML).toBe(
|
||||||
|
`<div>5 yes z</div><div>2 no z</div><div>3 no z</div>`
|
||||||
|
)
|
||||||
|
|
||||||
|
vm.z = 'zz'
|
||||||
|
await nextTick()
|
||||||
|
// should not update
|
||||||
|
expect(el.innerHTML).toBe(
|
||||||
|
`<div>5 yes z</div><div>2 no z</div><div>3 no z</div>`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,4 +1,4 @@
|
|||||||
import { VNodeChild } from '../vnode'
|
import { VNode, VNodeChild } from '../vnode'
|
||||||
import { isArray, isString, isObject } from '@vue/shared'
|
import { isArray, isString, isObject } from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
|
||||||
@ -52,13 +52,17 @@ export function renderList<T>(
|
|||||||
*/
|
*/
|
||||||
export function renderList(
|
export function renderList(
|
||||||
source: any,
|
source: any,
|
||||||
renderItem: (...args: any[]) => VNodeChild
|
renderItem: (...args: any[]) => VNodeChild,
|
||||||
|
cache?: any[],
|
||||||
|
index?: number
|
||||||
): VNodeChild[] {
|
): VNodeChild[] {
|
||||||
let ret: VNodeChild[]
|
let ret: VNodeChild[]
|
||||||
|
const cached = (cache && cache[index!]) as VNode[] | undefined
|
||||||
|
|
||||||
if (isArray(source) || isString(source)) {
|
if (isArray(source) || isString(source)) {
|
||||||
ret = new Array(source.length)
|
ret = new Array(source.length)
|
||||||
for (let i = 0, l = source.length; i < l; i++) {
|
for (let i = 0, l = source.length; i < l; i++) {
|
||||||
ret[i] = renderItem(source[i], i)
|
ret[i] = renderItem(source[i], i, undefined, cached && cached[i])
|
||||||
}
|
}
|
||||||
} else if (typeof source === 'number') {
|
} else if (typeof source === 'number') {
|
||||||
if (__DEV__ && !Number.isInteger(source)) {
|
if (__DEV__ && !Number.isInteger(source)) {
|
||||||
@ -71,17 +75,23 @@ export function renderList(
|
|||||||
}
|
}
|
||||||
} else if (isObject(source)) {
|
} else if (isObject(source)) {
|
||||||
if (source[Symbol.iterator as any]) {
|
if (source[Symbol.iterator as any]) {
|
||||||
ret = Array.from(source as Iterable<any>, renderItem)
|
ret = Array.from(source as Iterable<any>, (item, i) =>
|
||||||
|
renderItem(item, i, undefined, cached && cached[i])
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const keys = Object.keys(source)
|
const keys = Object.keys(source)
|
||||||
ret = new Array(keys.length)
|
ret = new Array(keys.length)
|
||||||
for (let i = 0, l = keys.length; i < l; i++) {
|
for (let i = 0, l = keys.length; i < l; i++) {
|
||||||
const key = keys[i]
|
const key = keys[i]
|
||||||
ret[i] = renderItem(source[key], key, i)
|
ret[i] = renderItem(source[key], key, i, cached && cached[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ret = []
|
ret = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cache) {
|
||||||
|
cache[index!] = ret
|
||||||
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
29
packages/runtime-core/src/helpers/withMemo.ts
Normal file
29
packages/runtime-core/src/helpers/withMemo.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { currentBlock, isBlockTreeEnabled, VNode } from '../vnode'
|
||||||
|
|
||||||
|
export function withMemo(
|
||||||
|
memo: any[],
|
||||||
|
render: () => VNode,
|
||||||
|
cache: any[],
|
||||||
|
index: number
|
||||||
|
) {
|
||||||
|
const cached = cache[index] as VNode | undefined
|
||||||
|
if (cached && isMemoSame(cached.memo!, memo)) {
|
||||||
|
// make sure to let parent block track it when returning cached
|
||||||
|
if (isBlockTreeEnabled > 0 && currentBlock) {
|
||||||
|
currentBlock.push(cached)
|
||||||
|
}
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
const ret = render()
|
||||||
|
ret.memo = memo
|
||||||
|
return (cache[index] = ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMemoSame(prev: any[], next: any[]) {
|
||||||
|
for (let i = 0; i < prev.length; i++) {
|
||||||
|
if (prev[i] !== next[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
@ -264,6 +264,7 @@ export { renderList } from './helpers/renderList'
|
|||||||
export { toHandlers } from './helpers/toHandlers'
|
export { toHandlers } from './helpers/toHandlers'
|
||||||
export { renderSlot } from './helpers/renderSlot'
|
export { renderSlot } from './helpers/renderSlot'
|
||||||
export { createSlots } from './helpers/createSlots'
|
export { createSlots } from './helpers/createSlots'
|
||||||
|
export { withMemo, isMemoSame } from './helpers/withMemo'
|
||||||
export {
|
export {
|
||||||
openBlock,
|
openBlock,
|
||||||
createBlock,
|
createBlock,
|
||||||
|
@ -182,6 +182,9 @@ export interface VNode<
|
|||||||
|
|
||||||
// application root node only
|
// application root node only
|
||||||
appContext: AppContext | null
|
appContext: AppContext | null
|
||||||
|
|
||||||
|
// v-for memo
|
||||||
|
memo?: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since v-if and v-for are the two possible ways node structure can dynamically
|
// Since v-if and v-for are the two possible ways node structure can dynamically
|
||||||
@ -221,7 +224,7 @@ export function closeBlock() {
|
|||||||
// Only tracks when this value is > 0
|
// Only tracks when this value is > 0
|
||||||
// We are not using a simple boolean because this value may need to be
|
// We are not using a simple boolean because this value may need to be
|
||||||
// incremented/decremented by nested usage of v-once (see below)
|
// incremented/decremented by nested usage of v-once (see below)
|
||||||
let isBlockTreeEnabled = 1
|
export let isBlockTreeEnabled = 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block tracking sometimes needs to be disabled, for example during the
|
* Block tracking sometimes needs to be disabled, for example during the
|
||||||
@ -692,7 +695,7 @@ export function normalizeVNode(child: VNodeChild): VNode {
|
|||||||
|
|
||||||
// optimized normalization for template-compiled render fns
|
// optimized normalization for template-compiled render fns
|
||||||
export function cloneIfMounted(child: VNode): VNode {
|
export function cloneIfMounted(child: VNode): VNode {
|
||||||
return child.el === null ? child : cloneVNode(child)
|
return child.el === null || child.memo ? child : cloneVNode(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeChildren(vnode: VNode, children: unknown) {
|
export function normalizeChildren(vnode: VNode, children: unknown) {
|
||||||
|
Loading…
Reference in New Issue
Block a user