Skip to content

来源于文章《极致舒适的 Vue 弹窗使用方案》,其源码在这里

源码

查看源码
useCommandComponent/index.ts源码
ts
export * from "./useCommandComponent"
export * from "./useCommandComponent"
useCommandComponent/useCommandComponent.ts源码
ts
import { AppContext, Component, ComponentPublicInstance, createVNode, getCurrentInstance, render, VNode } from 'vue';

export interface Options {
    visible?: boolean;
    onClose?: () => void;
    appendTo?: HTMLElement | string;
    [key: string]: unknown;
}

export interface CommandComponent {
    (options: Options): VNode;
    close: () => void;
}

const getAppendToElement = (props: Options): HTMLElement => {
    let appendTo: HTMLElement | null = document.body;
    if (props.appendTo) {
        if (typeof props.appendTo === 'string') {
            appendTo = document.querySelector<HTMLElement>(props.appendTo);
        }
        if (props.appendTo instanceof HTMLElement) {
            appendTo = props.appendTo;
        }
        if (!(appendTo instanceof HTMLElement)) {
            appendTo = document.body;
        }
    }
    return appendTo;
};

const initInstance = <T extends Component>(
    Component: T,
    props: Options,
    container: HTMLElement,
    appContext: AppContext | null = null
) => {
    const vNode = createVNode(Component, props);
    vNode.appContext = appContext;
    render(vNode, container);

    getAppendToElement(props).appendChild(container);
    return vNode;
};

export const useCommandComponent = <T extends Component>(Component: T): CommandComponent => {
    const appContext = getCurrentInstance()?.appContext;
    if (appContext) {
        const currentProvides = (getCurrentInstance() as any)?.provides;
        Reflect.set(appContext, 'provides', { ...appContext.provides, ...currentProvides });
    }

    const container = document.createElement('div');

    const close = () => {
        render(null, container);
        container.parentNode?.removeChild(container);
    };

    const CommandComponent = (options: Options): VNode => {
        if (!Reflect.has(options, 'visible')) {
            options.visible = true;
        }
        if (typeof options.onClose !== 'function') {
            options.onClose = close;
        } else {
            const originOnClose = options.onClose;
            options.onClose = () => {
                originOnClose();
                close();
            };
        }
        const vNode = initInstance<T>(Component, options, container, appContext);
        const vm = vNode.component?.proxy as ComponentPublicInstance<Options>;
        for (const prop in options) {
            if (Reflect.has(options, prop) && !Reflect.has(vm.$props, prop)) {
                vm[prop as keyof ComponentPublicInstance] = options[prop];
            }
        }
        return vNode;
    };

    CommandComponent.close = close;

    return CommandComponent;
};
import { AppContext, Component, ComponentPublicInstance, createVNode, getCurrentInstance, render, VNode } from 'vue';

export interface Options {
    visible?: boolean;
    onClose?: () => void;
    appendTo?: HTMLElement | string;
    [key: string]: unknown;
}

export interface CommandComponent {
    (options: Options): VNode;
    close: () => void;
}

const getAppendToElement = (props: Options): HTMLElement => {
    let appendTo: HTMLElement | null = document.body;
    if (props.appendTo) {
        if (typeof props.appendTo === 'string') {
            appendTo = document.querySelector<HTMLElement>(props.appendTo);
        }
        if (props.appendTo instanceof HTMLElement) {
            appendTo = props.appendTo;
        }
        if (!(appendTo instanceof HTMLElement)) {
            appendTo = document.body;
        }
    }
    return appendTo;
};

const initInstance = <T extends Component>(
    Component: T,
    props: Options,
    container: HTMLElement,
    appContext: AppContext | null = null
) => {
    const vNode = createVNode(Component, props);
    vNode.appContext = appContext;
    render(vNode, container);

    getAppendToElement(props).appendChild(container);
    return vNode;
};

export const useCommandComponent = <T extends Component>(Component: T): CommandComponent => {
    const appContext = getCurrentInstance()?.appContext;
    if (appContext) {
        const currentProvides = (getCurrentInstance() as any)?.provides;
        Reflect.set(appContext, 'provides', { ...appContext.provides, ...currentProvides });
    }

    const container = document.createElement('div');

    const close = () => {
        render(null, container);
        container.parentNode?.removeChild(container);
    };

    const CommandComponent = (options: Options): VNode => {
        if (!Reflect.has(options, 'visible')) {
            options.visible = true;
        }
        if (typeof options.onClose !== 'function') {
            options.onClose = close;
        } else {
            const originOnClose = options.onClose;
            options.onClose = () => {
                originOnClose();
                close();
            };
        }
        const vNode = initInstance<T>(Component, options, container, appContext);
        const vm = vNode.component?.proxy as ComponentPublicInstance<Options>;
        for (const prop in options) {
            if (Reflect.has(options, prop) && !Reflect.has(vm.$props, prop)) {
                vm[prop as keyof ComponentPublicInstance] = options[prop];
            }
        }
        return vNode;
    };

    CommandComponent.close = close;

    return CommandComponent;
};

Released under the MIT License.