首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用外部框架将Vue3自定义元素转换为Vue2应用程序

使用外部框架将Vue3自定义元素转换为Vue2应用程序
EN

Stack Overflow用户
提问于 2022-01-05 11:12:24
回答 1查看 960关注 0票数 3

我有一个用Vue2编写的应用程序,它还没有准备好升级到Vue3。但是,我想开始用Vue3编写一个组件库,并将组件导入到Vue2中,以便最终完成升级。

Vue 3.2+引入了工作良好的defineCustomElement,但是一旦我在Vue3环境中使用了附加到Vue实例的框架(例如类星体),它就会开始在Vue2应用程序中抛出错误,这可能是因为defineCustomElement(SomeComponent)的结果试图使用框架中应该附加到应用程序的东西。

我曾考虑过扩展HTMLElement并在connectedCallback上挂载应用程序,但后来我失去了反应性,不得不手动处理所有的道具/发射器/.就像这样:

代码语言:javascript
运行
复制
class TestQuasarComponentCE extends HTMLElement {
  // get init props
  const prop1 = this.getAttribute('prop1')

  // handle changes
  // Mutation observer here probably...

  const app = createApp(TestQuasarComponent, { prop1 }).use(Quasar)
  
  app.mount(this)
}

customElements.define('test-quasar-component-ce', TestQuasarComponentCE);

因此,最后的问题是--是否有可能将defineCustomElement与附加到应用程序的框架结合起来?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-01-11 20:27:30

所以,经过一段时间的挖掘,我想出了以下几点。

首先,让我们创建一个使用外部库的组件(在我的例子中是类星体)

代码语言:javascript
运行
复制
// SomeComponent.vue (Vue3 project)
<template>
  <div class="container">

    // This is the quasar component, it should get included in the build automatically if you use Vite/Vue-cli
    <q-input
      :model-value="message"
      filled
      rounded
      @update:model-value="$emit('update:message', $event)"
    />
  </div>
</template>

<script setup lang="ts>
defineProps({
  message: { type: String }
})

defineEmits<{
  (e: 'update:message', payload: string | number | null): void
}>()
</script>

然后我们准备要构建的组件(这就是魔术发生的地方)。

代码语言:javascript
运行
复制
// build.ts
import SomeComponent from 'path/to/SomeComponent.vue'
import { reactive } from 'vue'
import { Quasar } from 'quasar' // or any other external lib

const createCustomEvent = (name: string, args: any = []) => {
  return new CustomEvent(name, {
    bubbles: false,
    composed: true,
    cancelable: false,
    detail: !args.length
      ? self
      : args.length === 1
      ? args[0]
      : args
  });
};

class VueCustomComponent extends HTMLElement {
  private _def: any;
  private _props = reactive<Record<string, any>>({});
  private _numberProps: string[];

  constructor() {
    super()
    
    this._numberProps = [];
    this._def = SomeComponent;
  }
  
  // Helper function to set the props based on the element's attributes (for primitive values) or properties (for arrays & objects)
  private setAttr(attrName: string) {
    // @ts-ignore
    let val: string | number | null = this[attrName] || this.getAttribute(attrName);

    if (val !== undefined && this._numberProps.includes(attrName)) {
      val = Number(val);
    }

    this._props[attrName] = val;
  }
  
  // Mutation observer to handle attribute changes, basically two-way binding
  private connectObserver() {
    return new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.type === "attributes") {
          const attrName = mutation.attributeName as string;

          this.setAttr(attrName);
        }
      });
    });
  }
  
  // Make emits available at the parent element
  private createEventProxies() {
    const eventNames = this._def.emits as string[];

    if (eventNames) {
      eventNames.forEach(evName => {
        const handlerName = `on${evName[0].toUpperCase()}${evName.substring(1)}`;

        this._props[handlerName] = (...args: any[]) => {
          this.dispatchEvent(createCustomEvent(evName, args));
        };
      });
    }
  }
  
  // Create the application instance and render the component
  private createApp() {
    const self = this;

    const app = createApp({
      render() {
        return h(self._def, self._props);
      }
    })
      .use(Quasar);
      // USE ANYTHING YOU NEED HERE

    app.mount(this);
  }
  
  // Handle element being inserted into DOM
  connectedCallback() {
    const componentProps = Object.entries(SomeComponent.props);
    componentProps.forEach(([propName, propDetail]) => {
      // @ts-ignore
      if (propDetail.type === Number) {
        this._numberProps.push(propName);
      }

      this.setAttr(propName);
    });

    this.createEventProxies();
    this.createApp();
    this.connectObserver().observe(this, { attributes: true });
  }
}

// Register as custom element
customElements.define('some-component-ce', VueCustomElement);

现在,我们需要将它构建为库(我使用Vite,但也应该使用vue-cli )。

代码语言:javascript
运行
复制
// vite.config.ts

export default defineConfig({
  ...your config here...,
  build: {
    lib: {
      entry: 'path/to/build.ts',
      name: 'ComponentsLib',
      fileName: format => `components-lib.${format}.js`
    }
  }
})

现在,我们需要在有Vue3的上下文中导入构建的库,在我的例子中,index.html工作得很好。

代码语言:javascript
运行
复制
// index.html (Vue2 project)
<!DOCTYPE html>
<html lang="">
  <head>
    // Vue3 
    <script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>

    // Quasar styles
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
    <link href="https://cdn.jsdelivr.net/npm/quasar@2.4.3/dist/quasar.prod.css" rel="stylesheet" type="text/css">

     // Our built component
     <script src="path/to/components-lib.umd.js"></script>
  </head>

   ...rest of your html...
</html>

现在,我们已经准备好在Vue2 (或任何其他)代码库中使用我们的组件,就像我们习惯于进行一些小的更改一样,请查看下面的注释。

代码语言:javascript
运行
复制
// App.vue (Vue2 project)
<template>
  <some-component-ce
    :message="message" // For primitive values
    :obj.prop="obj" // Notice the .prop there -> for arrays & objects
    @update:message="message = $event.detail" // Notice the .detail here
  />
</template>

<script>
  export default {
    data() {
      return {
        message: 'Some message here',
        obj: { x: 1, y: 2 },
      }
    }
  }
</script>

现在,您可以在Vue3中使用Vue2组件:)

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70591905

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档