namespace
概念
在TypeScript中,namespace是一种用于组织代码得结构,主要用于将相关得功能(例如类、接口、函数等)组合在一起。它可以帮助避免命名冲突,尤其是在大项目中。
用法
1.定义命名空间
使用namespace关键字定义一个命名空间,并在其中包含类、接口、函数等等。以下举例是我在项目中定义得接口参数类型,因为我得接口参数类型是放在一个文件中进行维护得,所以难免会出现相同参数名不同类型得情况出现。所以这里可以使用namaspace来定义不同模块下得接口参数类型。
declare namespace Api {namespace Auth {type systemInfo = {logo: string;systemName: string;nickname: string;avatar: string;email: string;tenantId: string;}type Domain = {domainName: string;}}
}
2.访问命名空间中的成员
需要使用命名空间名来访问其成员,使用.操作符。
/*** 获取系统名称,logo等信息* @param domainName*/
export function fetchPostSystemInfo(params?: Api.Auth.Domain){return request<Api.Auth.systemInfo>({url: 'xxxxx',method: 'get',params})
}
以上举例限制fetchPostSystemInfo异步请求中传去的参数params的类型约束是Api.Auth.Domain,
调用时通过request<Api.Auth.systemInfo>
明确指定响应数据类型是Api.Auth.systemInfo,确保调用者可以在代码中获得类型提示和安全的类型检查。
3.使用
const systemInfo:Api.Auth.systemInfo = reactive({logo: '',systemName: '',nickname: '',avatar: '',email: '',
})async function getSystemInfo() {const { data, error } = await fetchPostSystemInfo({ domainName: window.location.hostname });if (!error && data) {Object.assign(systemInfo, data);}
}
类型定义扩展
泛型类型
实例1
declare namespace Api {namespace Auth {type emailToken<T = any> = {email: string;} & T;}
}
这段代码定义了一个泛型类型emailToken,它由一个对象组成,包含一个名为email的字符串属性,并且可以通过泛型参数T扩展。T默认是any类型。
具体含义是:
- emailToken<T = any >:这是一个泛型类型定义,其中T是一个可选的泛型参数,默认值是any。如果你在使用时不传入T,则T的类型会是any。
- { email : string } & T:这表示emailToken 类型是由两个部分组成的对象类型:
-
- 一个固定的属性email,类型是string
- 通过& T,可以扩展其他属性,这些属性来源T。也就是说,emailToken是一个包含email属性的对象,并且可以包含任何你传入的T类型的属性
举例如下:
type CustomToken = emailToken<{token: string }>;
// CustomToken 类型为 { email: string, token: string }
在上面的例子中,CustomToken类型包含两个属性:email和token,其中email是固定的,而token是通过传入T定义的。
使用:
/*** 获取验证码* @param email* return string*/
export function fetchGetEmailToken(params?: Api.Auth.emailToken) {return request<string>({url: '/usercenter/api/uc/otp/request/email',method: 'post',)}
}let emailToken: string = '';
async function getEmailToken(email: string) {const { data: token, error } = await fetchGetEmailToken({ email });if (!error && token) {emailToken = token || '';window.$message?.success?.('验证码发送成功')return emailToken;}
}
实例2
type Response<T = unKnown> = {statusCode: number;message: string;success: boolean;timestamp: string;result: T
}
- 泛型T:
-
- Response是一个泛型类型,接受一个类型参数T,默认值为unknowm。
- T可以是任何类型,允许Response在使用时具有灵活性
- 属性
-
- statusCode: string; :表示后端服务的请求状态码,例如 401,200
message: string;
:表示后端服务的响应消息,提供对状态的更详细说明。- result: T; :表示响应的具体数据,类型为T。这允许Response用于多种数据结构
示例1 返回简单数据
const simpleResponse:Response<string> = {statusCode: 200,message: "request is success",timestamp: "1729159210638",success: true,result: "Some string data"
}
在这个例子中,result是一个字符串
示例2 返回对象数据
type User = {id:number;name:string;
}
const userResponse:Response<User> = {statusCode: 200,message: "request is success",timestamp: "1729159210638",success: true,result: {id: 1,name: 'Hzz'}
}
在这个例子中,data是一个User类型的对象
示例3 返回数组数据
cosnt usersResponse: Response<User []> = {statusCode: 200,message: "request is success",timestamp: "1729159210638",success: true,result:[{ id: 1, name: "Alice" },{ id: 2, name: "Bob" }]
}
这里,data是一个User对象的数组
扩展用法
可以通过不同的方式扩展Response类型,以适应更复杂的需求。
添加分页功能
假设你希望响应支持分页,可以扩展Response来包括分页信息:
type PaginatedResponse<T> = {items: T[];total: number;
}type Response<T = unknowm> = {statusCode: number;message: string;success: boolean;timestamp: string;result: PaginatedResponse<T>; // 包含分页信息
}
type User = {id:number;name:string;
}
// 使用示例
const paginatedUserResponse:Response<User> = {statusCode: 200,message: "request is success",timestamp: "1729159210638",success: true,result:{items:[{id: 1, name: "Alice"},{id: 1, name: "Alice"}],total: 2}
}
结合错误信息
如果你希望在失败的情况下包含错误信息,可以扩展响应结构:
type ErrorResponse = {errorCode: string;errorDetails?: string;
}type Response<T = unknown> = {statusCode: number;message: string;success: boolean;timestamp: string;result?: T; // result是可选的error?: ErrorResponse;
}const errorResponse: Response = {statusCode: 200,message: "request is success",timestamp: "1729159210638",success: true,error:{errorCode: '404',errorDetails: "The user with the specified ID does not exist.",}
}
总结:
- Response是一个灵活的泛型类型,用于描述后端服务的响应,包括状态、消息和具体数据
- 通过将T用于result属性,可以轻松适应不同的数据结构
映射类型
declare module "@elegant-router/types" {export type RouteLayout = "base" | "blank" | "baseVariant";/*** route map*/export type RouteMap = {"root": "root";"403": "/403";"404": "/404";"500": "/500";"application": "/application";"application_develop": "/application/develop";"client": "/client";};/*** route key*/export type RouteKey = keyof RouteMap;/*** route path*/export type RoutePath = RouteMap[RouteKey];/*** custom route key*/ export type CustomRouteKey = Extract<RouteKey,| "root"| "not-found"| "exception"| "exception_403"| "exception_404">;
}
以上定义类型主要用来描述路由映射和键、值得类型,详细解析如下:
RouteMap
类型 是一个对象,它描述了路由得命名和对应关系
RouteKey
类型
- keyof 操作符用于获取某个类型得键的集合
- 在这里,keyof RouteMap将会提取RouteMap中所有的键,形成一个联合类型,也 就是
"403" | "404 | "500" | "application" | "application_develop" | "client"
。
因此,RouteKey
类型将会是:
type RouteKey = "403" | "404" | "500" | "application" | "application_develop" | "client";
在实际开发中常用于确保路由键与路由路径的映射是安全的和类型化的。例如,使用RouteKey
来引用某个路由时,TypeScript会确保你只能使用定义过的路由键,避免拼写错误等问题。
RoutePath
类型
在TypeScript中,使用方括号[]可以通过索引访问对象类型的值,语法如下:
T[K]
- T是一个对象类型
- K是T的键(可以是单个键或者键的联合类型)
通过T[k]这种索引访问方式,可以获取T中键K对应的值的类型。如果K是联合类型,结果会是所有键对应值的联合类型。
因此,在 exporttypeRoutePath = RouteMap[RouteKey]; 中
RouteMap
是一个对象类型 ,它的键是RouteKey,即 "403" | "404" | "500" | "application" | "application_develop" | "client" ,对应的值的路径字符串是对象的值,RouteMap[RouteKey]
会返回 RouteMap 中键的值的类型
RouteMap["root"] // "/"
RouteMap["403"] // "/403
...
最终结果:
type RoutePath = "/" | "/403" | "/404" | "/500" | "/application" | "/application/develop" | "/client";
总结:这种用法可以在需要动态获取某些键对应值的场景中确保类型安全,比如在路由系统中确保路径类型和路由键的一致性。
Exclude
类型工具
Exclude<T, U>是TypeScript内置的条件类型,它用于从类型T中排除那些可以分配给类型U的成员。它的定义是:
type Exclude<T, U> = T extends U ? never : T;
- 如果T中某个类型能够分配给U(T extends U 为 true),那么返回never类型(表示排除该成员)
- 否则,返回T本身
分析以下类型结果:
const RouteKey = "403" | "root" | "not-found" | "application" | "application_develop" | "client"; | "/client";
type I18nRouteKey = Exclude<RouteKey, 'root' | 'not-found'>;const RouteKey1 = 'root' | 'not-found'
type I18nRouteKey1 = Exclude<RouteKey1, 'root' | 'not-found'>;
I18nRouteKey:
- 从
RouteKey
中排除 'root' | 'not-found' 这两个键 - 那么 I18nRouteKey的最终结果是:type I18nRouteKey = "403" | "application" | "application_develop" | "client"; | "/client";
I18nRouteKey1:
- 从
RouteKey1
中排除 'root' | 'not-found' 这两个键 - 那么 I18nRouteKey1的最终结果是 type I18nRouteKey1 = never
结论:
I18nRouteKey1 的结果是never,是因为 因为 RouteKey1
中只有 "root"
和 "not-found"
。
当 RouteKey 中有其他键时, I18nRouteKey 会成为这些剩余键的联合类型。
Omit
工具类型
Omit<T, K>
是 TypeScript的内置工具类型,用于从类型T中排除指定的键K。
- T:要修改的原始类型
- K:要排除的键,可以是单个键或者多个键的联合类型
举例说明:
定义Breadcrumb结构
interface Menu {key: string;// 唯一标识符,用于识别菜单项label: string;// 显示给用户的菜单项标签i18nKey?: I18n.I18nKey | null;// 可选的国际化键,用于多语言支持routeKey: RouteKey;// 路由的键,表示该菜单项对应的路由routePath: RoutePath;// 路由的路径,指向该菜单项的具体路径icon?: () => VNode;// 可选的图标,返回一个虚拟节点(通常用于渲染图标)children?: Menu[];// 可选的子菜单项,数组类型,表示该菜单项下的子菜单
}type Breadcrumb = Omit<Menu, 'children'> & {options?: Breadcrumb[];
}
解释:
Omit<Menu, 'children'>
: 使用Omit工具类型从Menu接口中排除children属性。这样,Breadcrumb不会直接包含children。- & { options?: Breadcrumb[]; }:通过交叉类型将Omit的结果与一个新对象类型结合,这个新类型包含一个可选的属性options,其类型为Breadcrumb[],表示该面包屑导航项包含其他Breadcrumb对象的数组
Breadcrumb
的最终结构
type Breadcrumb = {key: string;// 继承自 Menulabel: string;// 继承自 Menui18nKey?: I18n.I18nKey | null;// 继承自 MenurouteKey: RouteKey;// 继承自 MenuroutePath: RoutePath;// 继承自 Menuicon?: () => VNode;// 继承自 Menuoptions?: Breadcrumb[]; // Breadcrumb[];
}
用法示例
假设我们要构建一个面包屑导航,可以使用Breadcrumb类型如下:
const breadcrumn:Breadcrumb = {key: "home",label: "Home",routeKey: "root",routePath: "/",icon: () => <span>🏠</span>, //可选的图标,返回一个虚拟节点options:[{key: "about",label: "About us",routeKey: "about",routePath: "/about"icon: () => <span>ℹ️</span>, // 子面包屑的图标options: [//....]},{key: "contact",label: "Contact",routeKey: "contact",routePath: "/contact",icon: () => <span>📞</span>, // 子面包屑的图标}]
}
说明
key
、label
、routeKey
和routePath
属性是必需的,定义了面包屑项的基本信息。icon
属性是一个可选的函数,返回一个虚拟节点(这里使用了简单的 Emoji 作为示例图标)。options
属性用于定义子面包屑项,支持嵌套结构。