wip(vapor): createDynamicComponent

This commit is contained in:
Evan You 2025-01-31 16:55:08 +08:00
parent cad7f0e583
commit 9f1025d854
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
8 changed files with 113 additions and 18 deletions

View File

@ -270,19 +270,19 @@ export function render(_ctx) {
`;
exports[`compiler: element transform > dynamic component > dynamic binding 1`] = `
"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
"import { createDynamicComponent as _createDynamicComponent } from 'vue';
export function render(_ctx) {
const n0 = _createComponentWithFallback(_resolveDynamicComponent(_ctx.foo), null, null, true)
const n0 = _createDynamicComponent(() => (_ctx.foo), null, null, true)
return n0
}"
`;
exports[`compiler: element transform > dynamic component > dynamic binding shorthand 1`] = `
"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
"import { createDynamicComponent as _createDynamicComponent } from 'vue';
export function render(_ctx) {
const n0 = _createComponentWithFallback(_resolveDynamicComponent(_ctx.is), null, null, true)
const n0 = _createDynamicComponent(() => (_ctx.is), null, null, true)
return n0
}"
`;

View File

@ -525,7 +525,7 @@ describe('compiler: element transform', () => {
`<component :is="foo" />`,
)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
@ -546,7 +546,7 @@ describe('compiler: element transform', () => {
const { code, ir, helpers } =
compileWithElementTransform(`<component :is />`)
expect(code).toMatchSnapshot()
expect(helpers).toContain('resolveDynamicComponent')
expect(helpers).toContain('createDynamicComponent')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,

View File

@ -64,9 +64,11 @@ export function genCreateComponent(
...inlineHandlers,
`const n${operation.id} = `,
...genCall(
operation.asset
? helper('createComponentWithFallback')
: helper('createComponent'),
operation.dynamic && !operation.dynamic.isStatic
? helper('createDynamicComponent')
: operation.asset
? helper('createComponentWithFallback')
: helper('createComponent'),
tag,
rawProps,
rawSlots,
@ -78,10 +80,14 @@ export function genCreateComponent(
function genTag() {
if (operation.dynamic) {
return genCall(
helper('resolveDynamicComponent'),
genExpression(operation.dynamic, context),
)
if (operation.dynamic.isStatic) {
return genCall(
helper('resolveDynamicComponent'),
genExpression(operation.dynamic, context),
)
} else {
return ['() => (', ...genExpression(operation.dynamic, context), ')']
}
} else if (operation.asset) {
return toValidAssetId(operation.tag, 'component')
} else {

View File

@ -0,0 +1,57 @@
import { shallowRef } from '@vue/reactivity'
import { nextTick } from '@vue/runtime-dom'
import { createDynamicComponent } from '../src'
import { makeRender } from './_utils'
const define = makeRender()
describe('api: createDynamicComponent', () => {
const A = () => document.createTextNode('AAA')
const B = () => document.createTextNode('BBB')
test('direct value', async () => {
const val = shallowRef<any>(A)
const { html } = define({
setup() {
return createDynamicComponent(() => val.value)
},
}).render()
expect(html()).toBe('AAA<!--dynamic-component-->')
val.value = B
await nextTick()
expect(html()).toBe('BBB<!--dynamic-component-->')
// fallback
val.value = 'foo'
await nextTick()
expect(html()).toBe('<foo></foo><!--dynamic-component-->')
})
test('global registration', async () => {
const val = shallowRef('foo')
const { app, html, mount } = define({
setup() {
return createDynamicComponent(() => val.value)
},
}).create()
app.component('foo', A)
app.component('bar', B)
mount()
expect(html()).toBe('AAA<!--dynamic-component-->')
val.value = 'bar'
await nextTick()
expect(html()).toBe('BBB<!--dynamic-component-->')
// fallback
val.value = 'baz'
await nextTick()
expect(html()).toBe('<baz></baz><!--dynamic-component-->')
})
})

View File

@ -0,0 +1,31 @@
import { resolveDynamicComponent } from '@vue/runtime-dom'
import { DynamicFragment, type Fragment } from './block'
import { createComponentWithFallback } from './component'
import { renderEffect } from './renderEffect'
import type { RawProps } from './componentProps'
import type { RawSlots } from './componentSlots'
export function createDynamicComponent(
getter: () => any,
rawProps?: RawProps | null,
rawSlots?: RawSlots | null,
isSingleRoot?: boolean,
): Fragment {
const frag = __DEV__
? new DynamicFragment('dynamic-component')
: new DynamicFragment()
renderEffect(() => {
const value = getter()
frag.update(
() =>
createComponentWithFallback(
resolveDynamicComponent(value) as any,
rawProps,
rawSlots,
isSingleRoot,
),
value,
)
})
return frag
}

View File

@ -41,11 +41,11 @@ export class DynamicFragment extends Fragment {
document.createTextNode('')
}
update(render?: BlockFn): void {
if (render === this.current) {
update(render?: BlockFn, key: any = render): void {
if (key === this.current) {
return
}
this.current = render
this.current = key
pauseTracking()
const parent = this.anchor.parentNode

View File

@ -444,8 +444,8 @@ export function isVaporComponent(
*/
export function createComponentWithFallback(
comp: VaporComponent | string,
rawProps: RawProps | null | undefined,
rawSlots: RawSlots | null | undefined,
rawProps?: RawProps | null,
rawSlots?: RawSlots | null,
isSingleRoot?: boolean,
): HTMLElement | VaporComponentInstance {
if (!isString(comp)) {

View File

@ -29,3 +29,4 @@ export {
getDefaultValue,
} from './apiCreateFor'
export { createTemplateRefSetter } from './apiTemplateRef'
export { createDynamicComponent } from './apiCreateDynamicComponent'