使用外部框架将Vue3自定义元素添加到Vue2应用程序中

本教程将介绍使用外部框架将Vue3自定义元素添加到Vue2应用程序中的处理方法,这篇教程是从别的地方看到的,然后加了一些国外程序员的疑问与解答,希望能对你有所帮助,好了,下面开始学习吧。

使用外部框架将Vue3自定义元素添加到Vue2应用程序中 教程 第1张

问题描述

我有一个用Vue2编写的应用程序,它还没有真正准备好升级到Vue3。但是,我想开始在Vue3中编写一个组件库,并将组件导入回Vue2中,最终在它准备好后进行升级。

Vue 3.2+引入了defineCustomElement,它工作得很好,但一旦我在Vue3环境(例如Quasar)中使用附加到Vue实例的框架,它就开始在Vue2应用程序中引发错误,可能是因为defineCustomElement(SomeComponent)的结果试图使用应该附加到应用程序的框架中的内容。

我想过扩展HTMLElement并在connectedCallback上挂载应用程序,但后来我失去了反应性,不得不手动处理所有道具/发射/..如下所示:

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与附加到应用程序的框架结合起来?

推荐答案

所以,经过一番挖掘,我得出了以下结论。

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

// 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>

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

// 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)

// 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运行良好。

// 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(或任何其他)代码库中使用我们的组件,方法与我们习惯的方式相同,只需进行一些微小的更改,请检查下面的注释。

// 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>

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

好了关于使用外部框架将Vue3自定义元素添加到Vue2应用程序中的教程就到这里就结束了,希望趣模板源码网找到的这篇技术文章能帮助到大家,更多技术教程可以在站内搜索。