是否听闻类型体操?类型体操在实际开发中有什么用处呢?本文带你粗略了解一下。体操主要在字符串转换那里,其他都是铺垫。
背景
vue项目中的路由定义
export default [
{
name: 'shapeAttrs',
path: '/web/components/shape-attrs',
component: () => import('@/views/components/shape-attrs.vue')
},
{
name: 'legend',
path: '/web/components/legend',
component: () => import('@/views/components/legend.vue')
}
];
希望通过变量来使用路径,比如
<router-link :to="PATH.componentsShapeAttrs">绘图属性</router-link>
那么我们需要定义一个变量 PATH
const PATH = {
componentsShapeAttrs: '/web/components/shape-attrs',
componentsLegend: '/web/components/legend'
}
根据DRY原则,我们通过方法来生成PATH
import componetsRoute from './componets'
function transform(routeList: Route[]) {
const result: Record<string, string> = {};
routeList.forEach((route) => {
const path = route.path;
const camelKey = path
.split('/')
.slice(2)
.map((item) => {
return item
.split('-')
.map((item) => {
return item.charAt(0).toUpperCase() + item.slice(1);
})
.join('');
})
.join('');
const key = camelKey.charAt(0).toLowerCase() + camelKey.slice(1);
result[key] = path;
});
return result;
}
export const PATH = transform(route)
但是,这样我们PATH的类型就变成了
type Path = {
[key: string]: string;
};
在使用时,就没有了代码提示。如何解决?
实现
1. 思路
首先确定我们期望的PATH的类型。
type Path = {
componentsShapeAttrs: '/web/components/shape-attrs',
componentsLegend: '/web/components/legend'
};
当然,跟期望的PATH的结构是一样的。
因此,我们也需要根据route定义,来生成PATH的类型。实现方式跟上面transform一样,只不过用类型体操实现一遍。
2.原理
实现之前,再看一遍路由原始定义
export default [
{
name: 'shapeAttrs',
path: '/web/components/shape-attrs',
component: () => import('@/views/components/shape-attrs.vue')
},
{
name: 'legend',
path: '/web/components/legend',
component: () => import('@/views/components/legend.vue')
}
];
然后分析一下transform是如何实现的。
-
遍历 routerList
-
将path转化为key
- 分隔字符串
- 取后两位
- 将每一项转为大驼峰
- 将两个大驼峰字符串拼接
- 转为小驼峰
-
将 { key: path }放到结果中
结构处理
首先,不考虑转换,如何生成type
const routeList = [
{
name: 'shapeAttrs',
path: '/web/components/shape-attrs',
component: '@/views/components/shape-attrs.vue'
},
{
name: 'legend',
path: '/web/components/legend',
component: '@/views/components/legend.vue'
}
] as const;
// 一、获取path列表
type KeyList = (typeof routeList)[number]['path'];
// type KeyList = "/web/components/shape-attrs" | "/web/components/legend"
// 二、遍历path列表,每一项作为结果的key与value
type Result = {
[K in KeyList]: K;
};
// type Result = {
// "/web/components/shape-attrs": "/web/components/shape-attrs";
// "/web/components/legend": "/web/components/legend";
// }
字符串转换
const str = '/web/components/shape-attrs';
type Str = typeof str;
type Split<S extends string, Delimiter extends string> = string extends S
? string[]
: S extends `${infer Start}${Delimiter}${infer Rest}`
? [Start, ...Split<Rest, Delimiter>]
: [S];
// 一、分隔字符串
type strList = Split<Str, '/'>;
// type strList = ["", "web", "components", "shape-attrs"]
type LastTwoElements<T extends string[]> = T extends [...infer _Init, infer Penultimate, infer Last] ? [Penultimate, Last] : [];
// 二、获取最后两位元素
type lastTwo = LastTwoElements<strList>;
// type lastTwo = ["components", "shape-attrs"]
// 工具方法,将字符串转为大驼峰
type CamelCase<S extends string> = S extends `${infer First}-${infer Rest}` ? `${Capitalize<First>}${CamelCase<Rest>}` : Capitalize<S>;
type camelCase = CamelCase<lastTwo[1]>;
// type camelCase = "ShapeAttrs"
// 三、将两个字符串转为大驼峰 然后拼接
type CamelCaseJoin<S extends string[]> = S extends [infer First, ...infer Rest]
? First extends string
? Rest extends string[]
? `${CamelCase<First>}${CamelCaseJoin<Rest>}`
: ''
: ''
: '';
type camelCaseJoin = CamelCaseJoin<lastTwo>;
// type camelCaseJoin = "ComponentsShapeAttrs"
// 四、首字母小写,变成小驼峰
type StrResult1 = Uncapitalize<camelCaseJoin>
// StrResult1 = "componentsShapeAttrs"
// 整合
type TransKey<T extends string> = Uncapitalize<CamelCaseJoin<LastTwoElements<Split<T, '/'>>>>;
type Result = TransKey<'/web/components/shape-attrs'>;
// type Result = "componentsShapeAttrs"
3.整合
type Split<S extends string, Delimiter extends string> = string extends S
? string[]
: S extends `${infer Start}${Delimiter}${infer Rest}`
? [Start, ...Split<Rest, Delimiter>]
: [S];
type CamelCase<S extends string> = S extends `${infer First}-${infer Rest}` ? `${Capitalize<First>}${CamelCase<Rest>}` : Capitalize<S>;
type LastTwoElements<T extends string[]> = T extends [...infer _Init, infer Penultimate, infer Last] ? [Penultimate, Last] : [];
type CamelCaseJoin<S extends string[]> = S extends [infer First, ...infer Rest]
? First extends string
? Rest extends string[]
? `${CamelCase<First>}${CamelCaseJoin<Rest>}`
: ''
: ''
: '';
type TransKey<T extends string> = Uncapitalize<CamelCaseJoin<LastTwoElements<Split<T, '/'>>>>;
export type Route = {
name: string;
path: string;
uri?: string;
component: any;
};
export type Route2PathType<T extends readonly Route[]> = {
[K in T[number]['path'] as TransKey<K>]: K;
};