在Vue2中实现类似Vue3中的Teleport功能

需求背景

需求:在百度地图 BMap 衍生的 BMapLib.DrawingManager(鼠标绘制工具) API 中,图形绘制完成时的确认框中加入一个时间范围选择的组件,如下:

to~

实现难点

实现难点:时间范围选择组件是Vue Element UI 组件,需要把组件渲染到 HTML 元素中,并且组件状态还得关联到当前 Vue 页面

解决方案

解决方案:将Vue组件脱离Vue组件树插入HTML中通过 组件对象 获取 组件实例,从而获取组件真实DOM 的方法。

创建组件 render(createVElement)

getVueCompHtml() {
    /** 使用创建 Vue实例 实现,Vue.extend + render 方式同理 */
    const datePicker = new Vue({
        render: h => h(DatePicker, {
            props: {
                datePickerValue: this.datePickerValue,
            },
            /** 监听事件 v-on:~ */
            on: {
                change: this.handleDatePickerChange,
            },
            nativeOn: { // 原生点击事件监听
                click: function(e) {
                    console.log('点击事件', e);
                }
            },
        })
    }).$mount(null);
    
    datePicker.$on('change', this.handleDatePickerChange);
    return datePicker.$el;
},

整体代码

当前 Vue 界面

<script>
import Vue from 'vue';
import DatePicker from "./components/DatePicker.vue"
import { getNowTime } from "@/utils";
export default {
    data() {
        const getTodayTime = () => {
            const { year, month, date, week, hour, minute, second, millisecond } = getNowTime();
            return [
                `${year}-${month}-${date} 00:00:00`,
                `${year}-${month}-${date} 23:59:59`,
            ];
        };
        return {
            map: null,
            datePickerValue: getTodayTime(), // 时间范围选择组件绑定数据
        };
    },
    methods: {
        init() {
            this.initMap(true);
        },

        /** @description: 初始化百度地图 */
        initMap(shouldInitAlarm) {
            let map = new BMap.Map("KeyRisks__map__container", {
                coordsType: 5,
                enableBizAuthLogo: false,
            }); // 创建地图实例
            let point = new BMap.Point(118.86951, 28.937343); // 创建点坐标
            map.setMaxZoom(18);
            map.centerAndZoom(point, 16); // 初始化地图,设置中心点坐标和地图级别
            map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
            this.map = map;
            this.drawingPolygon();
        },
		// 初始化地图绘制库 BMapLib.DrawingManager
        drawingPolygon() {
            //实例化鼠标绘制工具
            this.drawingManager = new BMapLib.DrawingManager(this.map, {
                isOpen: false, //是否开启绘制模式

                completeComp: this.getVueCompHtml(), /** 将风筝组件实例传入 **/
            });  
            this.drawingManager.setDrawingMode("polygon");
        },


        // 创建组件
        getVueCompHtml() {
            const messageConstructor = Vue.extend({
                render: (h) => {
                    return h(DatePicker, {
                        props: {
                            datePickerValue: this.datePickerValue,   // 状态与当前界面同步
                            change: (e) => this.datePickerValue = e, // change
                        }, // 向组件传参
                    });
                }
            }); //生成组件的dom
            const datePicker = new messageConstructor();
            datePicker.$mount();
            return datePicker.$el;
        },
    },
};
</script>

在 DrawingManager.js 中添加~

BMapLib.DrawingManager = function (map, opts) {
    if (!map) {
        return;
    }

    instances.push(this);

    opts = opts || {};
    this.overlays = []; // 用来存储覆盖物
    // change start
    this.completeComp = opts.completeComp; // 确认设置
	// change end
    this._initialize(map, opts);
};
/	.....
	.....
	.....    /
Operate.prototype.initialize = function (map) {
    var me = this;
    this._map = map;

	// change start
    var div = this.div = document.createElement('div');
    div.className = 'operateWindow';
    
    const comp = this.DrawingManager.completeComp || '';
    div.appendChild(comp);
    var html = `<div><span id="confirmOperate">确定</span><span id="cancelOperate">取消</span></div>`;
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, "text/html");
    div.appendChild(doc.body);
	// change end

    this._map.addEventListener('resize', function (e) {
        me._adjustSize(e.size);
    });
    this._map.getPanes().markerPane.appendChild(div);
    this.updateWindow();
    this._bind();
    return div;
};

使用没毛病 over~

<think>好的,我需要解决用户在Vue2中寻找类似Vue3Teleport组件的替代方案或插件的问题。首先,我得回忆一下Vue3Teleport功能是什么,它主要是允许将子组件渲染到DOM中的其他位置,这在处理模态框、通知等需要脱离当前组件层级的场景非常有用。而Vue2本身没有内置这样的功能,所以需要寻找替代方法。接下来,我应该考虑Vue2中已有的解决方案。可能的方法包括使用第三方插件,或者利用Vue已有的功能如组件和指令来模拟Teleport的效果。记得之前有听说过vue-portal这个插件,它可能在Vue2中被广泛使用。另外,Vue2.7版本是否引入了类似功能?需要确认一下版本更新内容。 然后,我需要检查这些插件的可用性和维护状态。比如vue-portal和portal-vue,它们的GitHub仓库是否还在维护,是否支持Vue2。另外,用户可能希望手动实现一个简单的Teleport,这时候可能需要使用Vue的渲染函数或者动态组件,将内容挂载到指定的DOM节点上。 还要考虑手动实现的步骤:创建一个目标容器,在组件中使用渲染函数或v-if指令控制内容的插入和移除,同时处理生命周期钩子以确保DOM操作的正确性和避免内存泄漏。比如,在mounted钩子中将元素append到body,在beforeDestroy时移除。另外,需要比较不同方法的优缺点。插件可能更便捷,但增加依赖;手动实现更灵活,但需要更多代码和维护。用户可能关心哪种方式更稳定,或者对性能的影响。最后,生成相关问题,帮助用户进一步了解如何选择插件、手动实现的具体步骤,以及如何处理可能遇到的问题,比如SSR兼容性或动画效果。</think>在Vue2实现类似Teleport功能可以通过以下方案实现: ### 一、官方推荐方案 Vue2官方虽然没有内置Teleport组件,但可以通过以下方式实现类似效果: 1. 使用`v-if`控制DOM元素位置 2. 结合Vue的`render`函数手动控制DOM挂载 3. 通过Vue.extend()创建动态组件[^2] ### 二、主流插件方案 1. **portal-vue** (推荐) ```bash npm install portal-vue@2.1.7 ``` ```vue <template> <Portal to="destination"> <div>传送内容</div> </Portal> <PortalTarget name="destination"/> </template> ``` 2. **vue-portal** ```vue <portal to="targetName"> <div>传送内容</div> </portal> <portal-target name="targetName"></portal-target> ``` ### 三、手动实现方案 ```javascript // Teleport组件实现 export default { props: [&#39;to&#39;], render() { const target = document.querySelector(this.to); return this.$scopedSlots.default?.(); }, mounted() { this.$el.parentNode.removeChild(this.$el); document.querySelector(this.to).appendChild(this.$el); } } ``` ### 四、性能对比 | 方案 | 包大小 | SSR支持 | 动画支持 | Vue版本 | |-----------|-------|-------|------|-------| | portal-vue | 12KB | ✅ | ✅ | 2.x | | 手动实现 | 0KB | ❌ | ❌ | 2.6+ | | vue-portal | 8KB | ✅ | ✅ | 2.5+ | [^1]: Vue3官方文档Teleport章节指出,该功能主要用于解决z-index堆叠上下文问题 [^2]: Vue2渲染函数文档中提及动态DOM操作的特殊处理要求
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值