跪拜 Guibai
← All articles
Frontend Framework · Functional Programming · Frontend

Business Popups Are Route Navigation, Not Child Components

By 阿懂在掘金 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Mid-to-backend systems and AI-driven interfaces both generate popups that must be invoked dynamically from arbitrary call sites and reused across different containers. Treating popup invocation as route navigation rather than child-component rendering eliminates the boilerplate and coupling that make these systems brittle, while the container-content split lets teams swap Dialog for Drawer or embed the same form inline without touching business logic.

Summary

The standard Vue pattern of welding a Dialog to its form with v-model breaks down as pages multiply: boilerplate balloons, invocation points scatter across deep component trees, and the same form can't be reused in a different container. vue-layerx treats popup invocation like router.push() — imperative scheduling — while content components remain pure, props-in/emits-out Vue components that know nothing about how they're hosted. A three-step API (createLayer pins the container, useDialog binds content, open() invokes) enforces a strict separation that makes swapping containers and reusing content independent operations.

The library pays explicit orchestration and runtime taxes: a four-level config merge chain (call-site > instance > content defaults > container defaults), a defineLayer macro that lets content declare its own container props, a LayerTemplate component that delivers SFC template slots across the container boundary without forcing JSX, and a closeOn contract that keeps close() out of content components entirely. Runtime concerns — context drift when DOM trees mount outside the main app, DevTools invisibility — are handled by a LayerHost that bridges appContext and an independent createApp that keeps layer trees debuggable.

Legacy monoliths where Dialog and form are still glued together can onboard gradually by treating the whole file as content with a no-container flag, preserving the target architecture while deferring the split. The design wasn't chosen for elegance; it's the narrow corridor that remains when all constraints — imperative scheduling, content purity, multi-role config merging, template-slot delivery, and runtime context integrity — are enforced simultaneously.

Takeaways
Business popup invocation should be imperative (like router.push), not declarative child-component rendering with v-model.
Content components must remain pure — props in, emits out — with no knowledge of their container; this lets the same form work in a Dialog, Drawer, or inline embed.
A three-step API (createLayer for container, useDialog for content binding, open() for invocation) maps to three distinct decision levels: project-wide, feature-level, and call-site.
Configuration merges across four priority tiers: open() params override instance config, which overrides defineLayer content defaults, which override createLayer container defaults.
The defineLayer macro lets a content component declare its own container props (title, width) so configuration lives with the content it describes.
LayerTemplate delivers SFC template slots across the container boundary without forcing teams into JSX or h() functions.
A closeOn contract wires emit('success') to automatic layer destruction, keeping close() out of content components so they stay host-agnostic.
LayerHost bridges the main app's appContext and provides when popup DOM trees mount outside the component tree, preventing context drift.
An independent createApp per layer keeps dynamically mounted components visible and debuggable in Vue DevTools.
Legacy monoliths with welded Dialog and form can onboard by treating the whole file as content with a no-container flag, deferring the split without forking the architecture.
Conclusions

The root problem isn't too much declarative boilerplate — it's that the industry has miscategorized popup invocation as child-component rendering rather than navigation scheduling.

AI interaction pages make imperative popup scheduling non-negotiable because which popup opens and when is determined at runtime by model intent, not hardcoded at page init.

Most popup libraries fail on one specific link in the chain: either they force JSX for slot delivery, inject close() into content and destroy reusability, or ignore context drift and DevTools collapse.

The design wasn't chosen for elegance; it's the narrow corridor that remains when all constraints are enforced simultaneously — a shape pushed out by boundary problems, not drawn up front.

Concepts & terms
Imperative popup invocation
Opening a popup via a function call (like router.push) rather than toggling a v-model boolean in a parent template. The invocation is imperative; the content rendered inside remains a standard declarative Vue component.
Container-content split
Separating the popup's chrome (Dialog, Drawer, mask, animation) from its business content (form, detail view). The container handles how something appears; the content handles what it does. This split lets the same content component be reused across different container types.
LayerHost
A vue-layerx concept that anchors a dynamically mounted popup instance to the component tree where it was created, bridging the main app's appContext and provide/inject chain so context (theme, i18n, routing) isn't lost when the DOM mounts outside the tree.
closeOn contract
A declarative wiring that maps content component events (like emit('success')) to automatic layer destruction, keeping the close() method out of the content component so it remains host-agnostic and reusable.
LayerTemplate
A vue-layerx component that lets an SFC template deliver UI into a container's named slot (e.g., footer buttons) across the container-content boundary, avoiding the need to write slot content in JSX or h() functions.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗