gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 01:54:36 25 4
gpt4 key购买 nike

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

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

我考虑过扩展 HTMLElement 并将应用程序安装到 connectedCallback 上,但后来我失去了 react 性,不得不手动处理所有 props/emits/..,如下所示:

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 组件了:)

关于vue.js - 使用外部框架将 Vue3 自定义元素添加到 Vue2 应用程序中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70591905/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com