先看这段强迫症爆炸的代码😤:
js
<script lang="ts" setup>
import { getCurrentInstance } from 'vue';
const { $loading } = getCurrentInstance()?.appContext.config.globalProperties ?? {}; // 繁琐,且还需要去额外地定义$loading的类型,不然ts会报类型错误
</script>
在 setup
中无法直接通过 this
访问全局属性(如 app.config.globalProperties
定义的属性),需借助 getCurrentInstance()
,导致代码出现了冗长的链式调用,而且每个组件里都要重新写这段导入和获取实例的代码。
缺点:
- 不支持tree-shaking
- 代码冗余
- 类型需要额外定义,否则报错
- 可读性差
改造第一步:Hook封装 🪝
js
// symbols.ts
export const GLOBAL_API = Symbol('unique_api_key')
// hooks/useGlobal.ts
import { ComponentInternalInstance, getCurrentInstance } from 'vue';
export default function useGlobal() {
const instance = getCurrentInstance() as ComponentInternalInstance;
return instance.appContext.config.globalProperties;
}
// 使用端:直接解放双手?
import useGlobal from "@/hooks/useGlobal";
const { $loading } = useGlobal(); // 还是需要去额外配置$loading的类型,否则会默认是any类型
在使用端还是会需要先import
才能获取到所需的全局属性。 不过用了自动引入插件配置后也就没有import
问题了。
优点:
- 代码量减少70%
- 统一访问入口
- 没有类型报错,但会默认为any类型
缺点:
- vue全局属性不支持tree-shaking
改造第二步:改用Provide/Inject 🥀
js
// main.ts
import { $loading } from "@/utils";
app.provide('$loading', $loading); // 减少了冗长的前置代码
// 使用端:更简单了吗?
const $loading = inject('$loading')!
作为一个比全局属性/Hooks开销更大的功能,在使用端与Hook封装的使用感基本无差别,仅仅是减少了入口代码量。而且经过测试,还是无法支持tree-shaking。
改造第三步:化繁为简 🍵
既然都已经弃用全局属性了,何不直接连全局概念也弃用?
js
// 使用端
// import { $loading } from "@/utils"; // 使用unplugin-auto-import配置后,省去import语句
$loading.start();
这种最简单引入的方式本身就能正常支持tree-shaking。
总结
- 变量/函数只在
<template></template>
中使用:可用全局属性(无需使用getCurrentInstance) - 变量需要响应式:用Hooks或者Provide/Inject
- 不需要响应式的变量/函数:直接
import
进阶
对于静态数据,直接用Vite配置
js
// vite.config.ts
export default defineConfig({
define: {
__APP_INFO__: JSON.stringify({
version: '1.2.0',
buildTime: new Date().toLocaleString()
})
}
})
// 使用端:零成本调用
console.log(__APP_INFO__.version) // 但需要单独类型定义
// global.d.ts
declare const __APP_INFO__: {
version: string;
buildTime: string;
};
优点:
- 零运行时开销:因为是构建时静态替换
- 自动tree-shaking
- 与业务代码解耦