Appearance
自定义插件
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。插件需要通过 app.use()
函数进行安装:
js
import { createApp } from 'vue'
const app = createApp({})
// 调用 app.use() 安装插件,其中:
// 第1个参数是被安装的“插件的对象”
// 第2个参数是传给插件的“配置选项”
app.use(myPlugin, {
/* 可选的选项 */
})
// 插件必须在 mount 之前进行安装
app.mount('#app')
定义插件
插件是一个包含 install
函数的对象,install
函数接收两个参数:第 1 个参数是 app 实例,第 2 个参数是传给插件的配置选项。
js
const myPlugin = {
install(app, options) {
// 插件的具体实现
}
}
插件的主要应用场景如下:
- 通过
app.component()
和app.directive()
注册一到多个全局组件或自定义指令; - 通过
app.provide()
使一个资源可被注入进整个应用; - 向
app.config.globalProperties
中添加一些全局实例属性或方法; - 一个可能上述三种都包含了的功能库 (例如 vue-router)。
通过插件注册全局组件
封装名为 EscookButton
的组件:
vue
<script setup>
defineProps({
type: {
default: 'primary',
// 属性值仅接收以下4个字符串
validator(value) {
return ['primary', 'danger', 'warning', 'default'].includes(value)
}
}
})
</script>
<template>
<!-- 点击事件可通过 Attribute 透传直接绑定到 button 上 -->
<button class="escook-button" :class="type"><slot /></button>
</template>
<style scoped>
.escook-button {
--primary: #006aff;
--danger: rgb(255, 79, 79);
--warning: rgb(255, 185, 55);
--default: rgb(241, 241, 241);
border: 2px solid #000;
height: 36px;
padding: 0 10px;
border-radius: 5px;
font-size: 14px;
cursor: pointer;
}
/* hover 时不透明度变为 0.9 */
.escook-button:hover {
opacity: 0.9;
}
/* 点击时不透明度变为 0.8 */
.escook-button:active {
opacity: 0.8;
}
/* 被禁用时不透明度变为 0.6 */
.escook-button[disabled] {
opacity: 0.6;
}
.primary {
background-color: var(--primary);
color: white;
border-color: hsl(from var(--primary) h s calc(l - 10));
}
.danger {
background-color: var(--danger);
color: white;
border-color: hsl(from var(--danger) h s calc(l - 10));
}
.warning {
background-color: var(--warning);
color: white;
border-color: hsl(from var(--warning) h s calc(l - 10));
}
.default {
background-color: var(--default);
color: #000;
border-color: hsl(from var(--default) h s calc(l - 10));
}
</style>
通过插件,全局注册此 EscookButton
组件:
js
// src/utils/escookPlugin.js
import EscookButton from '@/components4/EscookButton.vue'
export default {
install(app, options) {
app.component('EscookButton', EscookButton)
}
}
在 main.js
入口模块中,导入并安装此插件:
js
import { createApp } from 'vue'
import App from './App.vue'
// 1. 导入自定义的 Vue3 插件
import escookPlugin from './utils/escookPlugin'
const app = createApp(App)
// 2. 在 app 创建之后且 app.mount() 之前,安装自定义的 Vue 组件
app.use(escookPlugin, { upperCase: true })
app.mount('#app')
🌹 温馨提示 🌹
通过插件注册的组件,全局可用。
通过插件挂载全局指令
我们可以把之前封装的 v-lazy
指令,通过插件的形式挂载为全局指令。首先,把自定义指令相关的样式抽离为单独的 css 文件:
css
/* src/directives/v-lazy.css */
.escook-lazy-loading {
background: #fbfbfb url('@/assets/loading.gif?inline') no-repeat center;
background-size: 140px;
}
.escook-lazy-error {
background: #fbfbfb url('@/assets/bad.jpg?inline') no-repeat center;
background-size: 30px;
}
然后,把自定义指令封装为独立的 src/directives/v-lazy.js
模块:
js
import defaultImg from '@/assets/1x1.png?inline'
import './v-lazy.css'
const observer = new IntersectionObserver(
(entries) => {
// entries 是一个数组,存放着元素和视口相交的状态信息。
// 当判断到元素和视口相交时,有几个元素满足相交条件,
// entries 中就有几个相交的状态信息
entries.forEach((entry) => {
// 判断元素是否和视口相交
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src
// 相交1次之后,不再监听当前元素是否和视口相交
observer.unobserve(entry.target)
entry.target.addEventListener('load', function (e) {
e.target.classList.remove('escook-lazy-loading')
e.target.removeAttribute('data-src')
})
entry.target.addEventListener('error', function (e) {
e.target.src = defaultImg
e.target.classList.remove('escook-lazy-loading')
e.target.classList.add('escook-lazy-error')
})
}
})
},
{
// 交叉的阈值:
// 1 表示元素完整显示到视口中,才算相交
// 0.5 表示至少元素的一半在视口中可见,才算相交
// 0 表示元素只要有1个像素在视口中可见,就算相交
threshold: 0
}
)
export default {
// 绑定了 v-lazy 的 img 图片,
// 在将要被插入 DOM 前,为其添加 loading 类名并设置默认图片
beforeMount(el, binding) {
el.classList.add('escook-lazy-loading')
el.src = defaultImg
el.dataset.src = binding.value
observer.observe(el)
}
}
最后,改造 src/utils/escookPlugin.js
插件模块,全局注册自定义的 v-lazy
指令:
js
import EscookButton from '@/components4/EscookButton.vue'
// 导入自定义的 vLazy 指令
import vLazy from '@/directives/v-lazy.js'
export default {
install(app, options) {
app.component('EscookButton', EscookButton)
// 全局注册自定义的 v-lazy 指令
app.directive('lazy', vLazy)
}
}
通过插件注入全局数据
使用插件形参中的 app
参数,调用其上的 app.provide()
方法,可以全局提供数据给所有组件使用。
通过 Provide 向全局提供响应式数据
改造 src/utils/escookPlugin.js
插件,在头部区域导入 ref
响应式 API:
js
import { ref } from 'vue'
改造插件的 install
函数,在函数内部调用 app.provide()
方法,全局提供数据给所有组件使用:
js
export default {
install(app, options) {
app.component('EscookButton', EscookButton)
app.directive('lazy', vLazy)
// 全局提供数据,给所有组件使用,
// 第一个参数是数据名,由程序员自定义
// 第二个参数是要提供的数据,可以使用 ref 或 reactive API 提供响应式的数据
app.provide('$global', ref({ name: 'zs', age: 20 }))
}
}
在组件中,使用 vue 提供的 inject()
函数,注入全局数据到当前的组件中使用:
vue
<script setup>
import { inject } from 'vue'
const globalData = inject('$global')
</script>
<template>
<p>{{ globalData }}</p>
<!-- 点击此按钮,可以修改 Provide 提供的响应式数据, -->
<!-- 所有用到此响应式数据的组件,都会被更新渲染 -->
<button @click="globalData.age++">age++</button>
</template>
通过 options 提供需要注入的数据
在刚才的案例中,Provide 提供的数据是写死到 install
函数内部的。我们可以让用户通过 install
函数的第二个参数,传入要全局 Provide 的数据:
js
// main.js
app.use(escookPlugin, {
// 需要被全局提供的数据
globalData: { name: 'zs', age: 30 }
})
在插件的 install
函数中,我们可以使用第二个参数 options
来接收传入的数据,并把其包装为响应式数据,通过 Provide 提供到全局:
js
export default {
install(app, options) {
app.component('EscookButton', EscookButton)
app.directive('lazy', vLazy)
// 全局提供数据,给所有组件使用,
// 第一个参数是数据名,由程序员自定义
// 第二个参数是要提供的数据,可以使用 ref 或 reactive API 提供响应式的数据
// app.provide('$global', ref({ name: 'zs', age: 20 }))
app.provide('$global', ref(options.globalData))
}
}
通过插件挂载全局属性和方法
在插件的 install
函数中,除了可以使用 Provide/Inject 向全局提供数据之外,还可以通过 app.config.globalProperties
向全局挂载属性和方法。
挂载全局属性
把全局属性挂载到 app.config.globalProperties
上即可:
js
import EscookButton from '@/components4/EscookButton.vue'
import { vLazy } from '../utils/lazy.js'
import { ref } from 'vue'
export default {
install(app, options) {
// 往全局挂载属性
app.config.globalProperties.$msg = 'Hello liulongbin.'
app.component('EscookButton', EscookButton)
app.directive('lazy', vLazy)
app.provide('$global', ref(options.globalData))
}
}
在组件的 <template>
模板中,可以直接访问到全局挂载的属性值:
html
<p>{{ $msg }}</p>
在 <script setup>
中,需要先通过 getCurrentInstance()
函数获取到当前 SFC 组件的 instance
实例,再通过 instance.appContext.config.globalProperties
访问全局挂载的属性:
vue
<script setup>
import { getCurrentInstance } from 'vue'
// 1. 通过 getCurrentInstance() 获取当前组件实例
const instance = getCurrentInstance()
// 2. 通过 instance.appContext.config.globalProperties 访问全局挂载的属性
const msg = instance.appContext.config.globalProperties.$msg
console.log(msg)
</script>
挂载全局方法
挂载和使用全局方法的过程,与挂载和使用全局属性的过程完全一样。
例如,我们要全局挂载一个 $toast
方法,用来展示 toast 提示消息。首先,在 src/utils/
下创建 toast.js
模块,向外默认导出一个 toast 函数:
js
export default function (msg, duration = 3000) {
const toast = document.createElement('div')
toast.style.position = 'fixed'
toast.style.padding = '5px 15px'
toast.style.fontSize = '12px'
toast.style.color = '#fff'
toast.style.backgroundColor = '#000'
toast.style.boxShadow = '0px 0px 2px #2b2b2b'
toast.style.borderRadius = '3px'
toast.style.left = '50%'
toast.style.top = '50%'
toast.style.transform = 'translate(-50%, -50%)'
toast.style.zIndex = '999'
toast.style.cursor = 'default'
toast.textContent = msg
document.body.appendChild(toast)
setTimeout(() => {
document.body.removeChild(toast)
}, duration)
}
然后,在 escookPlugin.js
中导入刚才封装的 toast
函数,并把它挂载到全局:
js
// 1. 导入 toast 函数
import toast from './toast'
import EscookButton from '@/components4/EscookButton.vue'
import { vLazy } from '../utils/lazy.js'
import { ref } from 'vue'
export default {
install(app, options) {
app.config.globalProperties.$msg = 'Hello liulongbin.'
// 2. 往全局挂载 $toast 方法
app.config.globalProperties.$toast = toast
app.component('EscookButton', EscookButton)
app.directive('lazy', vLazy)
app.provide('$global', ref(options.globalData))
}
}
在组件的模板中,可以直接调用 $toast()
函数展示提示消息:
html
<button @click="$toast('Hello liulongbin!')">按钮</button> <button @click="showToast">按钮</button>
而在 <script setup>
中,需要先获取组件实例,再通过 instance.appContext.config.globalProperties
访问全局挂载的方法:
vue
<script setup>
import { getCurrentInstance } from 'vue'
// 1. 获取当前组件的实例
const instance = getCurrentInstance()
const showToast = () => {
// 2. 通过 instance.appContext.config.globalProperties 访问全局挂载的方法
instance.appContext.config.globalProperties.$toast('你好,刘龙宾.')
}
</script>