Basic Usage
This page shows several common usage patterns.
Dialogs are the most typical use case for imperative components, so the examples use the dialog component from element-plus. These examples can help you understand how to use other components in the same way.
To use the dialog component from element-plus, install the @vue-cmd/element-plus package.
# npm
npm install @vue-cmd/element-plus element-plus
# pnpm
pnpm add @vue-cmd/element-plus element-plus
# yarn
yarn add @vue-cmd/element-plus element-plusAppetizer
DialogContent Preview
The examples use the dialog content component DialogContent multiple times. This is what it looks like:
Do not interact with it yet. We have not really started; this component needs to work together with an imperative component, so clicking the button below will not do anything.
Also, import { useConsumer } from "@vue-cmd/core"; is used here to make the demos work across component libraries. In real usage, import it from the specific package you are using, for example import { useConsumer } from "@vue-cmd/element-plus".
The consumer Object
consumer is an important object. It is the bridge for communication between the inside and outside of a command component. It exposes as many potentially useful properties and methods as possible. Inside a dialog, use useConsumer to get it. Outside a dialog, call the command function created by useDialog to get it. They are the same object, so you will find that they are strictly equal.
// Outside
const dialog = useDialog();
const consumer = dialog(<DialogContent />);
// Inside
const consumer2 = useConsumer();
console.log(consumer === consumer2); // trueWith this, both the inside and outside have the ability to control the imperative component.
The shape of consumer is:
export interface IConsumer {
/** Component instance metadata */
meta?: Meta;
/** Reactive visibility state. Although hide and show are provided, this is still exposed for special scenarios such as watching visibility changes. */
visible: Ref<boolean>;
/** Hide */
hide: () => void;
/** Show */
show: () => void;
/** Destroy without advancing the promise state */
destroy: (external?: boolean) => void;
/** Promise */
promise: Promise<unknown>;
/** Promise executor resolve */
resolve: (val?: unknown) => void;
/** Promise executor reject */
reject: (reason?: unknown) => void;
/** Destroy and resolve the promise */
destroyWithResolve: (val?: unknown) => void;
/** Destroy and reject the promise */
destroyWithReject: (reason?: unknown) => void;
/** Unsubscribe */
off: (name: string | symbol, callback: (...args: unknown[]) => void) => void;
/** Subscribe */
on: (
name: string | symbol,
callback: (...args: unknown[]) => void,
config?: IOnConfig,
) => void;
/** Subscribe once */
once: (name: string | symbol, callback: (...args: unknown[]) => void) => void;
/** Emit */
emit: (name: string | symbol, ...args: unknown[]) => void;
/** UI library component instance ref */
componentRef?: Ref<any>;
/** Mounted HTML container element */
container: HTMLDivElement;
/** Component nesting stack */
stack: IConsumer[];
/** Current index in the component nesting stack */
stackIndex: number;
/** Mounted */
mounted: boolean;
/** Destroyed */
destroyed: boolean;
}Several important properties are introduced below. For the rest, refer to the type definitions.
metais similar to themetaproperty invue-routerand stores component-related information.visibleis a reactive boolean that indicates whether the component is visible. You can even control the component directly through this property instead of callingshoworhide.hidehides the component when you need to control visibility.showshows the component when you need to control visibility.containeris the container element.stackis the component nesting stack. It stores all currently nested component instances.stackIndexis the current index in the component nesting stack.componentRefis a reactive object that represents the native component instance. You can use it to access properties exposed by the native component, but when adapting a native component, you must assign the instance to it for this to work correctly.
Note
The Consumer object includes an event system, but it is not recommended for business logic. This system is mainly used for enhancing and wrapping imperative components and may change in future versions.
Creation and Destruction
Now let's introduce the concrete usage of imperative components.
Dialog invocation and destruction mainly use destroy, destroyWithResolve, and destroyWithReject.
destroyWithResolve and destroyWithReject advance the promise state to resolved or rejected, while destroy only destroys the dialog without advancing the promise state. If you are concerned about whether a Promise that never reaches a terminal state causes memory leaks, see this article: Does a Promise that never completes cause a memory leak?.
Tips
The first argument of dialog supports two forms:
- Pass a VNode directly, which is concise and powerful.
const dialog = useDialog();
dialog(<DialogContent />, {
// dialog(<DialogContent />, {
title: "hello world",
width: "90%",
});- Pass a function that returns a VNode. Use this when you need reactive props or run into special cases.
const dialog = useDialog();
dialog(() => <DialogContent />, {
// dialog(<DialogContent />, {
title: "hello world",
width: "90%",
});Show and Hide
hide only hides the component and does not actually destroy it. Some component close events are too limited to support this feature. element-plus supports it because we found the right timing.
Nesting
Start your journey into infinitely nested dialogs.
Native Component Features
All native component props, events, and methods are supported. Put props and events into attrs; in theory, this is fully compatible with all native component props and events.
Native Component Slots
All native component slots are supported, including named slots and scoped slots.
Component Ref
Pass a function that returns a VNode.
Communication
You can communicate with the component as usual through traditional emit and one-way data flow.
In the next chapter, however, you will see a more elegant communication method: Promise-based dialogs.
Open the dialog and emit an event. It is not as elegant as the Promise approach, but it can keep sending messages to the outside, while a Promise resolves only once.
provide and inject
This works the same way as Vue's provide and inject. You can also use provideProps to implement private injection, which limits the injected values to the command component and prevents components outside it from being polluted.
Usage is simple.
const dialog = useDialog();
dialog(<div>1</div>, {
provideProps: {
a: 1,
b: 2,
},
});el-drawer
Props Reference
For more props, refer to: element-plus dialog documentation. element-plus drawer documentation.
Place props and events from the official documentation into attrs.
We lift title and width/size to the top level because they are used so often, saving one layer of attrs. If these props are also present in attrs, they will be overridden.
const dialog = useDialog();
dialog(<DialogContent />, {
title: "Title",
width: "80%",
attrs: {
// If these props exist in attrs, they will be overridden
title: "Title",
width: "80%",
// Event
onXX: () => {},
},
});You can easily wrap this again and lift your frequently used props to the top-level object. Here is a simple example:
export const useDialogExample = () => {
const dialog = useDialog();
return (contentVnode, config: YourConfigInterface) => {
return dialog(contentVnode, {
attrs: {
// Your frequently used props
title: config?.title,
width: config?.width,
draggable: config?.draggable,
fullscreen: config?.fullscreen,
},
...config,
});
};
};