V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Jinnrry
V2EX  ›  Vue.js

请教一下写前端的各位大佬, vue 动态组件如何动态定义名字呢?

  •  
  •   Jinnrry · 127 天前 · 2650 次点击
    这是一个创建于 127 天前的主题,其中的信息可能已经有所发展或是发生改变。

    vue 版本:3.3

    背景:我项目里面希望给第三方提供一个页面。第三方通过网络接口的方式返回 html 代码,我程序里面把别人的 html 代码嵌入到我的页面中。

    目前想到的方案:

    1 、使用 v-html 标签嵌入。问题:这种方式嵌入,对方页面中如何调用我 vue 页面的方法属性呢?比如我这里有一个$http 变量是 axios 的实例,这个里面封装的验签相关处理,他必须用我这个$http 属性才能正常调用接口,不然他过不去验签。

    2 、使用 vue 的异步组件。目前还没研究明白怎么用

    下面是 demo 代码

    <template>
        <div id="main">
            <el-tabs type="border-card">
            <el-tab-pane v-for="(html,name) in pluginList"  :label="name">
                // 方案 1
                <div v-html="html">
    
                </div> 
                
                // 方案 2
                
                <AsyncComp /> // 这样写的话第二个 plugin 又叫啥名字呢?
                
            </el-tab-pane>
        </el-tabs>
        </div>
    </template>
    
    <script setup>
    import { reactive, ref, getCurrentInstance } from 'vue'
    const app = getCurrentInstance()
    const $http = app.appContext.config.globalProperties.$http
    import { defineAsyncComponent } from 'vue'
    
    
    const pluginList = reactive({})
    
    $http.get('/api/plugin/list').then(res => {
        if (res.data != null && res.data.length > 0  ) {
            pluginList[res.data] = ""
            getPlugHtml(res.data)
        }
    })
    
    
    
    const getPlugHtml = function(name){
    
    	 // 方案 1
    	 $http.post('/api/plugin/settings/'+name+"/index").then(res => {
                    if (res.data != null && res.data!=""  ) {
                        pluginList[name] = res.data
                    }else{
                        pluginList[name] = "Load Error!"
                    }
                })
    
    
    
    	// 方案 2. 但是 AsyncComp 这个名字怎么处理呢?这里换成变量以后,我模板里面的代码该怎么调用异步组件呢?
        const AsyncComp = defineAsyncComponent(() => {
            return new Promise((resolve, reject) => {
                $http.post('/api/plugin/settings/'+name+"/index").then(res => {
                    if (res.data != null && res.data!=""  ) {
                        resolve(res.data)
                    }else{
                        reject("Plugin Load Error!")
                    }
                })
                
            })
        })
    
    
    
    }
    
    
    </script>
    
    
    45 条回复    2024-07-24 18:00:46 +08:00
    saberlove
        1
    saberlove  
       127 天前
    您是否在寻找 QianKun?
    tog
        2
    tog  
       127 天前
    为什么不用 iframe ?
    发方案 1 按道理可行。
    weixind
        3
    weixind  
       127 天前
    需要定义个 bridge 。和 VUE 关系不大。
    Jinnrry
        4
    Jinnrry  
    OP
       127 天前
    @saberlove #1 太重了,就这么简单一个功能,不想引入这么重的一个依赖。三四年前用过这玩意,当时留下了难以磨灭的记忆
    Jinnrry
        5
    Jinnrry  
    OP
       127 天前
    @tog #2 主要是和我原来 vue 属性通讯的问题,使用 iframe 怎么把我的$http 之类的属性给到他的页面呢
    Jinnrry
        6
    Jinnrry  
    OP
       127 天前
    @tog #2 方案 1 的主要问题是 v-html 里面不执行 js 代码,只能插入 html 内容
    Jinnrry
        7
    Jinnrry  
    OP
       127 天前
    @weixind #3 大佬细说?这一句话理解不了啊
    bojackhorseman
        8
    bojackhorseman  
       127 天前
    听着有点像微前端的范畴。可以试试 micro-app ,接入很简单,就是主应用和子应用要按照框架约定好一些东西。
    horizon
        9
    horizon  
       127 天前
    全部暴露到 window 上啊。。
    daysv
        10
    daysv  
       127 天前
    搞那么麻烦, 全挂全局
    lisia
        11
    lisia  
       127 天前
    把$http 写入到 window 对象中。
    第三方 HTML 里面就可用在 HTML 里面插入 script 来获取了吧,不过这种安全风险有点大。
    murmur
        12
    murmur  
       127 天前
    这东西我在某个大型 OA 上见过,可以用 react 代码直接把内置组件换掉,还自带 babel
    op351
        13
    op351  
       127 天前
    @murmur 泛微的 ecode 吗?
    Jinnrry
        14
    Jinnrry  
    OP
       127 天前
    @murmur #12 我其实也就是干这个事,我希望第三方可以通过某些方式,替换掉我本身的一些组件。第三方可以通过这种方式引入新的功能、或者替换我以前的功能模块
    dfkjgklfdjg
        15
    dfkjgklfdjg  
       127 天前
    @Jinnrry #4 ,但是你的这个需求就不是一个简单的需求啊,得有一整套的方案。就是前两年常常提到的微前端。
    如果单纯为了临时解决就选择 iframe 。数据交互可以用 [postMessage]( https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage)
    dfkjgklfdjg
        16
    dfkjgklfdjg  
       127 天前
    @dfkjgklfdjg #15 ,最近去年开始微前端慢慢被认为是“伪需求”,[Web Component]( https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components) 方案开始流行起来了。
    moxxun
        17
    moxxun  
       127 天前 via iPhone
    看看 vue 文档动态组件,异步组件部分
    duanxianze
        18
    duanxianze  
       127 天前
    不想麻烦直接挂载全局就行了 稍微复杂一些就是提供一个 js 文件,里面封装你要提供的 API ,更复杂就是搞微前端,或者服务端渲染
    Jinnrry
        19
    Jinnrry  
    OP
       127 天前
    @moxxun #17 我看了起码 10 遍了。官方文档上面只有一句

    import { defineAsyncComponent } from 'vue'

    const AsyncComp = defineAsyncComponent(() => {
    return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */)
    })
    })
    // ... 像使用其他一般组件一样使用 `AsyncComp`

    这个 AsyncComp 怎么能换成变量,怎么局部声明组件,实在是没找到地方介绍
    moxxun
        20
    moxxun  
       127 天前 via iPhone
    @Jinnrry resolve 里可以插入 template 吧
    ccSir
        21
    ccSir  
       127 天前
    或者可以让第三方用 h() 创建组件,然后你封装一些方法供第三方调用就行。通过接口返回第三方写的 h() 在通过 defineComponent 创建组件,传入你自己定义的方法。
    ccSir
        22
    ccSir  
       127 天前
    虽然我自己也在用 v-html 。但是还是不太建议用这个,🐶
    bladey
        23
    bladey  
       127 天前
    <el-tabs v-model="activeName">
    <el-tab-pane v-for="(html, name) in pluginList" :label="name">
    <component :is="asyncCpt" :http="$http" />
    </el-tab-pane>
    </el-tabs>

    const activeName = ref('');
    const asyncCpt = ref(null);
    watch(activeName, async (newVal) => {
    asyncCpt.value = defineAsyncComponent(() => {
    return new Promise((resolve, reject) => {
    $http.post('/api/plugin/settings/' + newVal + '/index').then((res) => {
    if (res.data != null && res.data != '') {
    resolve(res.data);
    } else {
    reject('Plugin Load Error!');
    }
    });
    });
    });
    });
    bladey
        24
    bladey  
       127 天前
    @bladey 接口拿到的 html 不知道内容是什么样,不知道这样写行不行?
    ipwx
        25
    ipwx  
       127 天前
    你不如在这个组件里面

    onMounted(() => window.pluginContext = {'$html': $html, ... 任何你想要传递的属性});

    然后在你的插件里面通过 window.pluginContext 拿到上下文。
    Jinnrry
        26
    Jinnrry  
    OP
       127 天前
    @moxxun #20 不行

    const AsyncComp = defineAsyncComponent(() => {
    return new Promise((resolve, reject) => {
    resolve("<template> <div> hello world!! </div> </template>")
    })
    })


    Uncaught (in promise) Error: Invalid async component load result: <template> <div> hello world!! </div> </template>

    我理解,这玩意需要传一个组件的 js 对象?但是也没有哪里有说明,vue 组件格式的字符串,怎么能转对象
    Jinnrry
        27
    Jinnrry  
    OP
       127 天前
    @bladey #23 感谢! 拿到的 html 是这样的

    <template>
    <div>
    hello world
    </div>
    <script setup>
    console.log("test")
    </script>
    </template>

    也可以是标准的 html 格式。但是无论是哪张格式,都会报:Uncaught (in promise) Error: Invalid async component load result 这个错误。始终无法成功创建组件。

    这个 resolve 方法里面真的是传字符串吗?
    Jinnrry
        28
    Jinnrry  
    OP
       127 天前
    @ipwx #25 那通过什么方式展示插件的页面呢? v-html 不让执行 js ,这个异步组件我翻遍中英文资料,都没找到一篇关于网络加载的。我都怀疑这玩意根本不能加载网络组件了。所有能找到的示例,都是异步加载本地的组件,优化加载速度
    asdjgfr
        29
    asdjgfr  
       127 天前
    Jinnrry
        30
    Jinnrry  
    OP
       127 天前
    @asdjgfr #29 啊?这和 event 有啥关系?我也不需要处理 event 啊
    LuckyLauncher
        31
    LuckyLauncher  
       127 天前
    用 webcomponent ,直接用 esm 加载这个定义了 webcomponent 的 js 文件,然后 v-html 渲染这个 webcomponent 标签,不过这种方案三方的权限很大,如果你需要管控还是需要微前端
    Jinnrry
        32
    Jinnrry  
    OP
       127 天前
    https://github.com/hacke2/vue-append/tree/master

    找到一个插件,可以实现类似 v-html 的效果同时可以运行 js ,可惜是 vue2 的代码。看了下源码,思路是先加载 html ,然后找出 script 代码,使用 exec 运行一次
    Jinnrry
        34
    Jinnrry  
    OP
       127 天前
    @asdjgfr #33 确实可以,https://github.com/hacke2/vue-append/tree/master 这个插件基本上也是这个思路。但是这样自己搞,总觉得不够优雅,很容易出问题。
    lisongeee
        35
    lisongeee  
       127 天前
    你这个是拿到的是 .vue 文件,不是编译后的 .js 代码,要不叫后端返回的时候给你编译为 js ,这样你直接用 import('/api/vue.js') 去引入

    要不你就自己在前端使用 vue/compiler-sfc 将拿到的 .vue 字符串编译为组件对象
    Jinnrry
        36
    Jinnrry  
    OP
       127 天前
    @lisongeee #35 编译为 js ?那 html 部分怎么处理呢? 我现在设计考虑的点是

    1 、我这里能尽可能简单,同时能保证稳定性,不至于别人随便写点东西就崩了,或者别人崩了把我页面也搞崩了。

    2 、别人尽可能多的复用我的样式、包等,比如我页面引入了 element-ui ,别人可以直接用 element-ui 的组件、样式等,不需要重复引入。这样才能保证最终别人嵌入的页面和我原来的页面样式差不多。

    3 、对我和对别人,开发调试都简单。
    linglingling
        37
    linglingling  
       127 天前
    多年前端,全栈都会一些。你这个要求,既要简单,又要安全,还要通讯等等,实现不了。符合你需求的是后端模板,如 Thymeleaf 。
    bladey
        38
    bladey  
       126 天前
    @Jinnrry #27
    这样的话试试这个写法,应该可以。这类需求还是第一次见,有一点一直没想明白,你想让第三方用你封装的 axios ,那在他的组件里调用时,拿到的 baseurl 、token 应该都是你的系统的,应该毫无意义吧,除非他的组件里请求的接口也在你的系统里,通过后端转发拿他系统的数据,不然这么搞我是没想明白到底要干什么
    watch(activeName, async (newVal) => {
    $http.post('/api/plugin/settings/' + newVal + '/index').then((res) => {
    if (res.data != null && res.data !== '') {
    // 创建一个 Blob ,用于生成组件的 URL
    const blob = new Blob([res.data], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);

    // 动态加载组件
    asyncCpt.value = defineAsyncComponent(() => {
    return import(url);
    });
    }
    });
    });
    horizon
        39
    horizon  
       126 天前
    @Jinnrry #36
    你需要的是一个插件系统
    如果还需要保证稳定性的话,那你需要自己开发一个沙盒
    最简单还是 iframe 吧。。
    ipwx
        40
    ipwx  
       126 天前
    我觉得你想在页面上给一块区域,让服务器传来的 HTML 和 JS 能跑起来还是挺容易的。

    拿到 DOM Element ,然后一边 xxx.innerHTML = 'HTML 部分'; 另一边 createElement('script') 然后把 JS 放进去跑。

    但是感觉楼主你不会。

    另一方面如果你要让 Vue 组件也跑起来,那大概得把整套 JS Module 都丢到页面上…… 算了这条路你还是自己趟吧。
    Jinnrry
        41
    Jinnrry  
    OP
       126 天前
    @ipwx #40 在 vue 项目里面找到一个 issues ,https://github.com/vuejs/core/discussions/6939

    准备按这个思路搞,拿到字符串,调用 vue 的相关编译方法,动态编译生成组件
    DesnLee
        42
    DesnLee  
       126 天前
    @Jinnrry #27 你这个为什么 template 包着 setup ?
    Jinnrry
        43
    Jinnrry  
    OP
       126 天前
    @bladey #38 比如他需要请求我系统的用户信息接口,拿到用户相关资料。然后拿用户资料跟他系统的 id 关联,接着处理他直接的逻辑

    <template>
    <div class="hello">
    <MyComponent ></MyComponent>
    </div>
    </template>

    <script setup>
    import { defineAsyncComponent } from 'vue'

    // 创建一个 Blob ,用于生成组件的 URL
    const blob = new Blob([`<template> <div> hello world! </div> </template>`], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);

    const MyComponent = defineAsyncComponent(() => {
    return import(url);
    });

    </script>

    安装你的思路写了要给 demo ,这个会报错 Cannot find module 'blob:http://localhost:8080/f795bf43-d4b9-4332-ac54-0139c19ce4e1'
    at http://localhost:8080/js/app.js:191:11
    Jinnrry
        44
    Jinnrry  
    OP
       126 天前
    @DesnLee #42 回复的时候手敲的,敲错了
    unco020511
        45
    unco020511  
       126 天前
    你把所有需要桥接的对象和方法都挂载到 window 上不行吗
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5355 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 01:19 · PVG 09:19 · LAX 17:19 · JFK 20:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.