TS 类型
介绍
下面介绍 Teek 内置的 TS 声明文件(.d.ts
文件)和一些声明操作。
Teek 内置的 TS 声明文件在 src/types
目录下。
TS 配置文件
TS 的配置文件为根目录下的 tsconfig.json
文件,Teek 已经在该文件里添加了项目需要的 .d.ts
、.ts
、.tsx
等文件的扫描和提示,所以你无需关心自己写的 TS 文件是否有提示。
如果你的 TS 文件所在的路径不在 TS 配置文件配置的扫描范围内,那么需要在 TS 配置文件的 include 添加扫描路径。
env.d.ts
.vue
、.scss
文件不是常规的文件类型,typescript
无法识别,所以 Teek 在 src/types/env.d.ts
添加了这些文件类型的支持。
除此之外,import.meta.env.***
的 TS 类型提示也在该文件定义。
/// <reference types="vite/client" />
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<object, object, any>;
export default component;
}
declare module "*.scss" {
const scss: Record<string, string>;
export default scss;
}
// import.meta.env 环境变量
interface ImportMetaEnv {
/**
* 项目端口号
*/
VITE_PORT: number;
/**
* 运行 npm run dev 时自动打开浏览器
*/
VITE_OPEN: boolean;
/**
* 是否生成包分析文件
*/
VITE_REPORT: boolean;
/**
* 是否开启 gzip 压缩
*/
VITE_BUILD_GZIP: boolean;
/**
* 打包过程是否删除 console
*/
VITE_DROP_CONSOLE: boolean;
/**
* 打包过程是否删除 debugger
*/
VITE_DROP_DEBUGGER: boolean;
/**
* 是否生成 sourcemap 文件
*/
VITE_SOURCEMAP: boolean;
/**
* 打包输出目录,默认 dist
*/
VITE_OUT_DIR: string;
/**
* 是否将 css 切割
*/
VITE_CSS_SPLIT: boolean;
/**
* 是否加载所有 element-plus 样式,false 则为按需加载
*/
VITE_LOAD_ALL_EP_STYLE: boolean | string;
/**
* 是否加载所有 element-plus 组件,false 则为按需加载
*/
VITE_LOAD_ALL_EP_COMPONENTS: boolean | string;
/**
* 当前环境
*/
VITE_NODE_ENV: string;
/**
* 接口跟地址
*/
VITE_API_URL: string;
/**
* 静态资源路径
*/
VITE_PUBLIC_PATH: string;
/**
* 打包文件的压缩类型
*/
VITE_COMPRESSION: "none" | "gzip" | "brotli" | "both" | "gzip-clear" | "brotli-clear" | "both-clear";
/**
* 路由模式
*
* @default 'H5'
*/
VITE_ROUTER_MODE: string;
/**
* 是否启用 WebSocket
*/
VITE_WEBSOCKET: boolean | string;
/**
* WebSocket 连接 URL,仅当 VITE_WEBSOCKET 为 true 生效
*/
VITE_WEBSOCKET_URL: string;
/**
* 路由权限模式,默认前端 frontend
*/
VITE_ROUTE_ACCESS_MODE: "frontend" | "backend" | "both";
}
declare global {
interface ImportMeta {
readonly env: ImportMetaEnv;
}
}
auto-import.d.ts
Teek 安装了 unplugin-auto-import
依赖,来实现 Vue 生态常用 API 自动导入,如 ref
、computed
、watch
等。
该依赖在 src/types
下自动生成 auto-import.d.ts
文件来支持该特性。
如:
<script setup lang="ts" name="App">
const isOpen = ref(true);
</script>
不需要像下面手动引入 ref:
<script setup lang="ts" name="App">
import { ref } from "vue";
const isOpen = ref(true);
</script>
auto-components.d.ts
Teek 安装了 unplugin-vue-components
依赖,来实现组件自动导入,如 Element Plus 组件和 src/components
下的组件。
该依赖在 src/types
下自动生成 auto-components.d.ts
文件
components.d.ts
Teek 自定义了一些组件并注册到全局里,但是在使用的时候默认没有 TS 语法提示,如没有 props 需要的属性提示,所以在该文件里自动项目注入这些提示。
如 Teek 全局注册了 Auth、Role 组件,则需要添加 TS 提示,则在 components.d.ts
里添加:
// vue 实例全局属性
declare module "vue" {
export interface GlobalComponents {
Role: typeof import("./components/Permission/role.vue")["default"];
Auth: typeof import("../components/core/permission/src/auth.vue")["default"];
Icon: typeof import("./components/Icon/index.vue")["default"];
}
interface ComponentCustomProperties {
// element plus 的变量
scope: any;
$index: number;
}
}
export {};
这样使用 Auth、Role 组件时,会有 TS 提示,如组件里的 props 提示。
提示
引入了 unplugin-vue-components
插件后,该文件实际上可以不需要了,因为 unplugin-vue-components
插件自动生成的 src/types/auto-components.d.ts
文件已包含了这些全局组件的类型定义。
plugins.d.ts
在项目开发过程种,我们安装的部分依赖可能没有集成 Typescript,导致在 import vContextmenu from "v-contextmenu"
的时候,会报错:无法找到 v-contextmenu 的声明文件 ...
因此我们可以利用在该文件注册对应 TS 类型定义:
declare module "v-contextmenu";
这样 import vContextmenu from "v-contextmenu"
的时候就不会报错。
router.d.ts
Teek 基于自身场景重写了 Vue 的路由 TS 提示,在 Vue 的路由 TS 提示基础上进行拓展,添加了更多的适用于 Teek 的其他 TS 提示。
在 路由 里已经介绍了 Teek 强大的 meta
功能,当使用 route.meta.
时,会有很多属性的类型提示,这些属性类型是在这个文件里定义的。
enhance.d.ts
我们知道 TS 自带的几个 API:
- Partial 可选
- Pick 提取
- Omit 去除
- Required 必选
- ...
Teek 根据常用场景基于这些 API 进行组装,更便于操作 TS 的提示。
/**
* 将对象的某个属性变为可选,如:
interface User {
name: string;
age: string;
gender: string;
}
// gender 变为可选
let user: PartialKey<User, "gender"> = {
name: "",
age: "",
};
// age 和 gender 变为可选
let user: PartialKey<User, "age" | gender"> = {
name: "",
};
*/
declare type PartialKey<T, U extends keyof T> = Pick<T, Exclude<keyof T, U>> & Partial<Pick<T, U>>;
/**
* 指定属性变为必选
*/
declare type RequiredKey<T, U extends keyof T> = Pick<T, Exclude<keyof T, U>> & Required<Pick<T, U>>;
/**
* 指定的属性为必选,其他属性都变为可选
*
* 如 RequiredKey<User, "name">
* 则只有 name 是必填,age 和 gender 变为可选
*/
declare type RequiredKeyPartialOther<T, U extends keyof T> = Partial<Pick<T, Exclude<keyof T, U>>> &
Required<Pick<T, U>>;
/**
* 指定属性变为只读
*/
declare type ReadOnlyKey<T, U extends keyof T> = Pick<T, Exclude<keyof T, U>> & Readonly<Pick<T, U>>;
/**
* 对象类型
*/
declare type Record<string, any><T = string, K = any> = Record<T extends null | undefined ? string : T, K>;
/**
* 可空类型
*/
declare type Nullable<T> = T | null;
/**
* 对象 key 转小写后返回字面量,
*
* 如 interface User = {NAME: string, AGE: string}
* keyLowercase<User> 返回 "name" | "name"
*/
declare type keyLowercase<T> = Lowercase<keyof T>;
/**
* 对象 key 转大写后返回字面量
*
* 如 interface User = {name: string, age: string}
* keyUppercase<User> 返回 "NAME" | "AGE"
*/
declare type keyUppercase<T> = Uppercase<keyof T>;
/**
* 创建一个类型,用于将键名添加 'on' 前缀
* 如 type Search = {search: [params: string], reset: [params: string]}
*
* type SearchOn = keyOnPrefix<Search>
*
* SearchOn 为 {onSearch: (params: string) => void, onReset: (params: string) => void}
*
* 该类型可以用于 Emits 类型
*/
declare type keyOnPrefix<T> = {
[K in keyof T as `on${Capitalize<K>}`]: T[K] extends readonly any[] ? (...t: T[K]) => void : never;
};
如果你需要根据自己的使用场景组装 API,则可以参考上面内容。
http.d.ts
关于全局 Axios 请求的 TS 提示,Teek 放在了 src/types/http.d.ts
文件里。
declare namespace httpNs {
interface Response<T = any> {
/**
* 状态码
*/
code: number;
/**
* 状态码信息
*/
status: string;
/**
* 消息
*/
message: string;
/**
* 数据
*/
data: T;
}
}
这是后台返回的一个 http 响应格式。
我们如何使用呢?
import http from "@/common/http";
export interface BackstageMenuList {
imageIcon: string;
menuCode: string;
pagePath: string;
menuName: string;
menuUrl: string;
parentMenuCode: string;
seq: number;
children?: BackstageMenuList[];
}
export const getMenuList = () => {
return http.request<httpNs.Response<BackstageMenuList>>({
url: "/test",
method: "post",
data: {},
});
};
Axios 请求的部分请看:请求,这里只是演示如何使用 http.d.ts
的内容。
在 http.d.ts
声明的响应格式,我们无需 import
引入,因为 declare namespace http
声明一个命名空间后,TS 会自动将 http
全局注入到 Teek 里,在任意文件里都无需引入。
警告
任意一个 .d.ts
文件出现 export
关键词,无论里面定义了多少 declare
类型,都不会全局注入到项目里,只能手动 import 引入。
所以 http.d.ts
文件里不建议出现 export
关键词,否则关于 Axios 的响应 TS,都需要手动 import
,如下:
export declare namespace httpNs {
interface Response<T = any> {
/**
* 状态码
*/
code: number;
/**
* 状态码信息
*/
status: string;
/**
* 消息
*/
message: string;
/**
* 数据
*/
data: T;
}
}
则需要手动引入 http。
import type { http } from "@/types/http";
import axios from "@/config/request";
export interface BackstageMenuList {
imageIcon: string;
menuCode: string;
pagePath: string;
menuName: string;
menuUrl: string;
parentMenuCode: string;
seq: number;
children?: BackstageMenuList[];
}
export const getMenuList = () => {
return axios.request<httpNs.Response<BackstageMenuList>>({
url: "/test",
method: "post",
});
};
window.d.ts
Teek 给项目添加了一些全局 TS 类型,这样无需 import
引入就可以使用类型。
因为 Teek 往 window 上添加了一些变量,如果直接在项目里使用 window.xx 是没有 TS 类型提示的,所以 Teek 在 src/types/window.d.ts
中添加了对 window 的类型定义:
interface Log {
base: (text: any) => void;
info: (textOrTitle: any, content?: any) => void;
primary: (textOrTitle: any, content?: any) => void;
success: (textOrTitle: any, content?: any) => void;
warning: (textOrTitle: any, content?: any) => void;
danger: (textOrTitle: any, content?: any) => void;
error: (content: any) => void;
table: (data: any[]) => void;
picture: (url: string, scale?: number) => void;
}
declare global {
interface Navigator {
browserLanguage: string;
}
interface Window {
webkitCancelAnimationFrame;
mozCancelAnimationFrame;
oCancelAnimationFrame;
msCancelAnimationFrame;
webkitRequestAnimationFrame;
mozRequestAnimationFrame;
oRequestAnimationFrame;
msRequestAnimationFrame;
log: Log;
}
/**
* 平台的名称、版本、依赖、最后构建时间的类型提示
*/
const __APP_INFO__: {
pkg: {
name: string;
version: string;
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
};
lastBuildTime: string;
};
const log: Log;
}
export {}; // 扩展 global 而不是覆盖
当使用 window.log
或者直接使用 log
的 API 时,就会有类型提示,如 log.base
。
基于该文件,你也可以自定义一些全局 TS 类型。