Vue插件开发

Plugins 开发

插件 | Vue.js

前言

Vue3中,一个插件通常是一个对象

通常我们开发的插件通过全局导出

1
export default myPlugin

从而在项目的入口文件中使用

1
app.use(myPlugin)

基础开发

plugins/

MyButton.vue

1
2
3
<template>
<button>我的按钮</button>
</template>

index.ts

1
2
3
4
5
6
import MyButton from './MyButton.vue'
export default{
install:(app,options)=>{
app.component('myButton', MyButton)
}
}

main.js

1
2
3
import MyButton from './plugins/MyButton.ts'

create(App).use(MyButton).mount('#app')

Views/

Helloworld.vue

1
2
3
4
5
<template>
<div class="home">
<myButton></myButton>
</div>
</template>

多插件开发

components/

Button/

MyButton.vue
1
2
3
<template>
<button>我的按钮</button>
</template>
MyButtonGroup.vue
1
2
3
4
5
<template>
<div class="er-button-group">
<slot></slot>
</div>
</template>
index.ts

导出 MyButtonMyButtonGroup 组件

使用 withInstall 函数为它们添加安装方法

1
2
3
4
5
6
7
8
import Button from "./MyButton.vue";
import ButtonGroup from "./MyButtonGroup.vue";
import { withInstall } from "@tyche/utils"; //在 install.ts 里

export const ErButton = withInstall(Button);
export const ErButtonGroup = withInstall(ButtonGroup);

export * from "./types";

utils/

install.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import type { App, Plugin, Directive } from "vue";
//App(Vue 应用实例)、Plugin(插件接口)、Directive(自定义指令)
import { noop } from "lodash-es";
//空函数,通常用于占位符

type SFCWithInstall<T> = T & Plugin;//将任意类型 T 与 Plugin 接口合并
//任何实现了这个类型的对象不仅具有原始类型 T 的所有属性和方法
//还需要实现 Plugin 接口中的 install 方法

export const withInstall = <T>(component: T) => {
//👇将传入的组件转换为 SFCWithInstall<T> 类型,并为其添加 install 方法
(component as SFCWithInstall<T>).install = (app: App) => {//为component添加install方法,该方法接收一个app应用实例
const name = (component as any)?.name || "UnnamedComponent";//获取组件的名称。如果组件没有定义 name 属性,则默认使用 "UnnamedComponent"
app.component(name, component as SFCWithInstall<T>);//使用应用实例的方法 .component() 全局注册该组件后,就可以在应用的任何地方使用。
};
return component as SFCWithInstall<T>;//返回经过扩展后的组件对象
};

style.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { isNumber, isString } from "lodash-es";
import { debugWarn } from "./error";

const SCOPE = "utils/style" as const;

const isStringNumber = (val: string): boolean => {
if (!isString(val)) {
return false;
}
return !Number.isNaN(Number(val));
};
export function addUnit(value?: string | number, defaultUnit = "px") {
if (!value) return "";
if (isNumber(value) || isStringNumber(value)) {
return `${value}${defaultUnit}`;
}
if (isString(value)) {
return value;
}
debugWarn(SCOPE, "binding value must be a string or number");
}

error.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { isString } from "lodash-es";

class ErUIError extends Error {
constructor(message: string) {
super(message);
this.name = "ErUIError";
}
}

export function throwError(scope: string, msg: string) {
throw new ErUIError(`[${scope}] ${msg}`);
}

export function debugWarn(error: Error): void;
export function debugWarn(scope: string, msg: string): void;
export function debugWarn(scope: string | Error, msg?: string) {
if (process.env.NODE_ENV !== "production") {
const err = isString(scope) ? new ErUIError(`[${scope}] ${msg}`) : scope;
console.warn(err);
}
}

index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export * from './install'
import { defineComponent } from "vue";
import { isFunction } from "lodash-es";

export const RenderVnode = defineComponent({
props: {
vNode: {
type: [String, Object, Function],
required: true,
},
},
setup(props) {
return () => (isFunction(props.vNode) ? props.vNode() : props.vNode);
},
});

export const typeIconMap = new Map([
["info", "circle-info"],
["success", "check-circle"],
["warning", "circle-exclamation"],
["danger", "circle-xmark"],
["error", "circle-xmark"],
]);

export * from "./install";
export * from "./error";
export * from "./style";

core/

componens.ts

集合所有需要全局注册的组件,并将其作为一个插件数组导出

1
2
3
4
5
6
7
8
9
10
11
12
13
import {
ErButton,
ErButtonGroup,
...
} from "@tyche/components";

import type { Plugin } from 'vue'

export default [
ErButton,
ErButtonGroup,
...
] as Plugin[]

makeInstaller.ts

创建一个通用的安装器,用于批量安装组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import type { App, Plugin } from "vue";
import { INSTALLED_KEY } from "@tyche/constants";//标记应用是否已经安装过组件库
import { each, get, set } from "lodash-es";
//遍历/获取/设置

//传入组件
export default function makeInstaller(components: Plugin[]) {
const install = (app: App) => {
if (get(app, INSTALLED_KEY)) return;//app 对象中存在 INSTALLED_KEY 属性,说明组件库已经安装过,直接返回
set(app, INSTALLED_KEY, true);//使用 set 将 INSTALLED_KEY 设置为 true,标记该应用已经安装了组件库

each(components, (c) => {//遍历传入的 components 数组
app.use(c);
});
};

return install;
}

注:以下是constants/index.ts

1
export const INSTALLED_KEY = Symbol("INSTALLED_KEY");

index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { library } from "@fortawesome/fontawesome-svg-core";
import { fas } from "@fortawesome/free-solid-svg-icons";
import makeInstaller from './makeInstaller'
import components from './components'
import printLogo from "./pringLogo";
import '@tyche/theme/index.css'

printLogo();

library.add(fas);
const installer = makeInstaller(components);//通用的安装器,用来批量安装组件

export * from '@tyche/components'
export default installer

docs/

components

button.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
title: Button
description: Button 组件文档

next:
link: /components/collapse
text: Collapse 折叠面板

prev:
link: /get-started
text: 快速开始
---

# Button 按钮

常用的操作按钮。

## 基础用法

使用 `type``plain``round``circle`来定义按钮的样式。

::: preview
demo-preview=../demo/button/Basic.vue
:::

demo

button
Basic.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<p>
<er-button>Default</er-button>
<er-button type="primary">Primary</er-button>
<er-button type="success">Success</er-button>
<er-button type="info">Info</er-button>
<er-button type="warning">Warning</er-button>
<er-button type="danger">Danger</er-button>
</p>

<p>
<er-button plain>Plain</er-button>
<er-button type="primary" plain>Primary</er-button>
<er-button type="success" plain>Success</er-button>
<er-button type="info" plain>Info</er-button>
<er-button type="warning" plain>Warning</er-button>
<er-button type="danger" plain>Danger</er-button>
</p>

<p>
<er-button round>Round</er-button>
<er-button type="primary" round>Primary</er-button>
<er-button type="success" round>Success</er-button>
<er-button type="info" round>Info</er-button>
<er-button type="warning" round>Warning</er-button>
<er-button type="danger" round>Danger</er-button>
</p>

<p>
<er-button icon="search" circle />
<er-button type="primary" icon="edit" circle />
<er-button type="success" icon="check" circle />
<er-button type="info" icon="message" circle />
<er-button type="warning" icon="star" circle />
<er-button type="danger" icon="trash" circle />
</p>
</template>