vue实现js调用式组件
Posted 在厕所喝茶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue实现js调用式组件相关的知识,希望对你有一定的参考价值。
vue实现js调用式组件
前言
本文主要讲解vue2
和vue3
如何创建js调用式组件
。其中vue3
包含了两种实现方案
vue2 创建 js 调用式组件
关键实现函数是Vue.extend
,通过Vue.extend
可以创建一个子类组件出来
我们以实现一个loading-bar
组件为例,步骤如下:
- 新建一个
.vue
文件,在里面编写loading-bar
组件,实现你想要的的UI
效果
<template>
<div class="loading-bar">
<div
:style=" transform: `translateX(-$100 - totalProgress%)` "
class="loading-bar-progress"
:class=" 'is-error': isError "
>
<div class="loading-bar-peg"></div>
</div>
<div class="loading-bar-spinner" v-if="showSpinner">
<div
:style=" 'animation-timing-function': easing "
class="loading-bar-icon"
:class=" 'is-icon-error': isError "
></div>
</div>
</div>
</template>
<script>
export default
data()
return
// 加载器(转圈圈的那个东西),运动形式
easing: "linear",
// 是否为错误类型
isError: false,
// 显示加载器
showSpinner: true,
// 加载的总进度
totalProgress: 0,
// 每次前进的百分比
percentNum: 0,
// 加载速度
speed: 5,
;
,
;
</script>
- 通过
Vue.extend
继承loading-bar
组件
import LoadingBar from "./loading-bar.vue";
const LoadingBarConstructor = Vue.extend(LoadingBar);
LoadingBarConstructor
是一个构造函数(类),我们可以添加一些额外的方法
// 设置全局配置信息
LoadingBarConstructor.prototype.config = function config(options)
//todo
;
// 初始化加载进度条
LoadingBarConstructor.prototype.init = function init()
//todo
;
// 显示加载进度条
LoadingBarConstructor.prototype.start = function start()
//todo
;
// 关闭加载进度条
LoadingBarConstructor.prototype.end = function end()
//todo
;
// 显示错误进度条
LoadingBarConstructor.prototype.error = function error()
//todo
;
- 初始化 vue 实例
const instance = new LoadingBarConstructor();
instance.totalProgress = 10;
instance
实例就是一个vue
实例,你可以通过这个实例访问或者修改组件中的easing
,isError
,totalProgress
等响应式数据
- 生成并挂载 DOM
const vm = instance.$mount();
document.body.appendChild(vm.$el);
instance.$mount()
是生成DOM
的,可通过vm.$el
访问DOM
最终还需要把生成出来的DOM
挂载到页面上
- 销毁
document.body.removeChild(vm.$el);
instance.$destroy();
instance = null;
销毁的时候需要先移除页面上的DOM
元素,然后在进行实例的销毁
- 最终效果
对于生成并挂载 DOM
,销毁
这些流程,我们可以进行一些封装,对外屏蔽一些实现的细节,最终代码如下:
const LoadingBarConstructor = Vue.extend(LoadingBar);
LoadingBarConstructor.prototype.destroyTimer = function destroyTimer()
if (this.timer)
clearInterval(this.timer);
this.timer = null;
;
LoadingBarConstructor.prototype.destroyRemoveTimer =
function destroyRemoveTimer()
if (this.removeTimer)
clearTimeout(this.removeTimer);
this.removeTimer = null;
;
// 设置全局配置信息
LoadingBarConstructor.prototype.config = function config(options)
Object.keys(options).forEach((key) =>
if (key === "isError" || key === "totalProgress")
return;
this[key] = options[key];
);
;
// 初始化加载进度条
LoadingBarConstructor.prototype.init = function init()
this.destroyTimer();
this.totalProgress = 0;
this.isError = false;
this.vm = this.$mount();
document.body.appendChild(this.vm.$el);
return this;
;
// 显示加载进度条
LoadingBarConstructor.prototype.start = function start()
this.init();
this.timer = setInterval(() =>
// 小于90的时候才进行前进
if (this.totalProgress < 90)
this.totalProgress += (this.percentNum || Math.random()) * this.speed;
, 100);
;
// 关闭加载进度条
LoadingBarConstructor.prototype.end = function end()
if (!timer)
this.init();
// 先把总进度设置为100,让他走完
this.totalProgress = 100;
this.destroyRemoveTimer();
this.removeTimer = setTimeout(() =>
this.destroyTimer();
document.body.removeChild(this.vm.$el);
, 200);
;
// 显示错误进度条
LoadingBarConstructor.prototype.error = function error()
this.end();
this.totalProgress = 100;
this.isError = true;
;
- 使用方式
const instance = new LoadingBarConstructor();
instance.start();
setTimeout(() =>
instance.end();
, 3000);
vue3 创建 js 调用式组件
在vue3
中,已经废弃了vue.extend
全局 api。我们可以通过createApp
或者createVNode
+render
的方式去实现
createApp
使用过 vue3
的同学应该知道,createApp
是用来创建一个app
实例的
我们以实现一个loading-bar
组件为例,步骤如下:
- 新建一个
.vue
文件,编写loading-bar
组件,实现想要的UI
效果
<template>
<div class="loading-bar">
<div
:style=" transform: `translateX(-$100 - totalProgress%)` "
class="loading-bar-progress"
:class=" 'is-error': isError "
>
<div class="loading-bar-peg"></div>
</div>
<div class="loading-bar-spinner" v-if="showSpinner">
<div
:style=" 'animation-timing-function': easing "
class="loading-bar-icon"
:class=" 'is-icon-error': isError "
></div>
</div>
</div>
</template>
<script lang="ts">
import defineComponent, ref from "vue";
export default defineComponent(
setup()
// 加载器(转圈圈的那个东西),运动形式
const easing = ref("linear");
// 是否为错误类型
const isError = ref(false);
// 显示加载器
const showSpinner = ref(true);
// 加载的总进度
const totalProgress = ref(0);
// 每次前进的百分比
const percentNum = ref(0);
// 加载速度
const speed = ref(5);
return
easing,
isError,
showSpinner,
totalProgress,
percentNum,
speed,
;
,
);
</script>
- 创建
app
实例
import LoadingBar from "./index.vue";
const app = createApp(LoadingBar);
- 创建
vue
实例
const rootContainer = document.createElement("div");
const vm = app.mount(rootContainer);
vm.isError = true;
vm
实例就是vue
实例,可以通过这个实例访问或者修改组件中的easing
,isError
,totalProgress
等响应式数据
注意:app.mount 中的第一个参数要求必须传入一个根元素,但是这个根元素不能是 document.body
- 挂载 DOM
document.body.appendChild(rootContainer);
// 或者
// document.body.appendChild(vm.$el);
在挂在 DOM 的时候,你可以选择把根元素rootContainer
挂在上去,或者把组件的 DOM 元素挂在上去。区别只是在于rootContainer
在组件的 DOM 元素外层多了一个div
元素
- 销毁
app.unmount();
rootContainer.remove();
销毁的时候,直接调用app
实例上面的unmount
函数进行销毁即可,同时需要把 DOM 进行移除
- 最终效果
根据上面的步骤,我们进行一次封装,对外屏蔽一些实现的细节,最终代码如下:
import isPlainObject from "@packages/utils";
import App, createApp from "vue";
import LoadingBar from "./index.vue";
interface RootData
easing?: string;
isError?: boolean;
showSpinner?: boolean;
totalProgress?: number;
percentNum?: number;
speed?: number;
class LoadingBarConstructor
private app: App | null = null;
private vm: any | null = null;
private timer: number | null = null;
private removeTimer: number | null = null;
private rootContainer: htmlElement | null = null;
private options: RootData = ;
private init(options: RootData = )
this.destroy();
this.app = createApp(LoadingBar);
this.rootContainer = document.createElement("div");
// 这个是为了获取组件实例,方便后面对组件变量动态操作
this.vm = this.app.mount(this.rootContainer);
const config: any =
...this.options,
...options,
;
for (const key in config)
if (Object.prototype.hasOwnProperty.call(config, key))
this.vm[key] = config[key];
document.body.appendChild(this.rootContainer);
return this;
private destroy()
this.app?.unmount();
this.rootContainer?.remove();
this.app = null;
this.vm = null;
this.rootContainer = null;
// 设置全局配置信息
config(options: RootData)
if (isPlainObject(options))
this.options = options;
return this;
private destroyTimer()
if (this.timer)
clearInterval(this.timer);
this.timer = null;
private destroyRemoveTimer()
if (this.removeTimer)
clearTimeout(this.removeTimer);
this.removeTimer = null;
// 显示加载进度条
start(options: RootData)
this.init(options);
this.timer = window.setInterval(() =>
// 小于90的时候才进行前进
if (this.vm.totalProgress < 90)
this.vm.totalProgress +=
(this.vm.percentNum || Math.random()) * this.vm.speed;
, 100);
return this;
// 关闭加载进度条
end()
if (!this.timer)
this.init();
// 先把总进度设置为100,让他走完
this.vm.totalProgress = 100;
this.destroyRemoveTimer();
this.removeTimer = window.setTimeout(() =>
this.destroyTimer();
this.destroy();
, 200);
return this;
// 显示错误进度条
error()
this.end();
this.vm.totalProgress = 100;
this.vm.isError = true;
return this;
- 使用方式
const instance = new LoadingBarConstructor();
instance.start();
setTimeout(() =>
instance.end();
, 3000);
createVNode + render
createVNode
和render
在官方文档中提及的比较少。通过阅读element-plus
源码可以发现element
是通过createVNode
和render
来实现 js 调用式组件。
createVNode(component,props)
:第一个参数为组件,第二个参数为组件的props
render(vm,rootContainer)
:第一个参数为VNode
,第二个参数为组件的根元素
我们以实现一个loading
组件为例,步骤如下:
- 创建组件
因为我们需要访问或者修改组件的响应式数据,所以我们不能通过.vue
文件来创建组件,createVNode
是无法获取得到组件的实例
import defineComponent, reactive, Transition from "vue";
const data = reactive(
// 加载文案
text: "",
// 是否全屏
fullscreen: true,
// 控制是否显示
visible: false,
// 背景
background: "",
// loading颜色
loadingColor: "",
// 文本颜色
textColor: "",
);
const destroySelf = () =>
// todo
;
const loadingComponent = defineComponent(
setup()
return data, destroySelf ;
,
render()
return (
<Transition name="fade" onAfterLeave=destroySelf>
data.visible ? (
<div
class=["loading-mask", "is-fullscreen": data.fullscreen ]
style= backgroundColor: data.background || ""
>
<div class="loading-content">
<span
class="loading-icon"
style=
"border-top-color": data.loadingColor || "",
"border-right-color": data.loadingColor || "",
></span>
data.text ? (
<span
style= color: data.textColor || ""
class="loading-text"
>
data.text
</span>
) : null
</div>
</div>
) : null
</Transition>
);
,
);
- 生成并挂载 DOM
const div = document.createElement("div");
const vm = createVNode(loadingComponent);
render(vm, div);
document.body.appendChild(vm.el as HTMLElement);
// 或者
// document.body.appendChild(div);
在render
函数中,第二个参数不能是document.body
要注意的是vm
是VNode
实例,并不是组件的实例
- 销毁
document.body.removeChild(vm.el as HTMLElement);
render(null, div);
- 最终效果
根据上面的步骤,我们进行一次封装,对外屏蔽一些实现的细节,最终代码如下:
import pickObject from "@packages/utils";
import
createVNode,
defineComponent,
reactive,
render,
Transition,
from "vue";
import Options from "./types";
const createComponent = () =>
const data = reactive(
// 加载文案
text: "",
// 是否全屏
fullscreen: true,
// 控制是否显示
visible: false,
// 背景
background: "",
// loading颜色
loadingColor: "",
// 文本颜色
textColor: "",
);
const div = document.createElement("div");
const destroySelf = () =>
document.body.removeChild(vm.el as HTMLElement);
render(null, div);
;
const loadingComponent = defineComponent(
setup()
return data, destroySelf ;
,
render()
return (
<Transition name="fade" onAfterLeave=destroySelf>
data.visible ? (
<div
class=["loading-mask", "is-fullscreen": data.fullscreen ]
style= backgroundColor: data.background || ""
>
<div class="loading-content">
<span
class="loading-icon"
style=
"border-top-color": data.loadingColor || "",
"border-right-color": data.loadingColor || "",
></span>
data.text ? (
<span
style= color: data.textColor || ""
class="loading-text"
>
data.text
</span>
) : null
</div>
</div>
) : null
</Transition>
);
,
);
const vm = createVNode(loadingComponent);
render(vm, div);
const open = (options?: Options) =>
if (!options)
return;
const object = pickObject(options, [
"text",
"fullscreen",
"background",
"loadingColor",
"textColor",
"fullscreen",
]);
// 修改响应式数据
Object.keys(object).forEach((key) =>
(data as any)[key] = object[key as keyof Options];
);
// 已经是显示状态就不需要走下面了
if (data.visible)
return;
// 添加上去
document.body.appendChild(vm.el as HTMLElement);
// 显示
data.visible = true;
;
const close = () =>
if (!data.visible)
return;
data.visible = false;
;
return
open,
close,
;
;
- 使用
const instance = createComponent();
instance.open();
setTimeout(() =>
instance.close();
, 3000);
两种方式对比
如果只是实现一个js调用的组件,推荐使用createVNode
+render
的方式。因为createApp
会生成一个app
实例,会初始化一些无关要紧的东西,比如app.use
,app.config.globalProperties
等属性和函数,这些东西根本就用不上,每次都会进行一次初始化,会造成不必要的性能浪费
以上是关于vue实现js调用式组件的主要内容,如果未能解决你的问题,请参考以下文章