引言
如何对一个对象操作并且可以得到期望值一直是新手的一个痛点,今天我就来说一说这个事情。
最近在开发一个菜单插件,需要定义一组菜单的操作按钮,其中有一个需要展示菜单事件定义的操作。
Object.keys 的操作
对象的操作一般都很简单,无非就是取值还是赋值,以及一些key、value的操作。但是融入了ts之后,这些操作就变得复杂起来。首先,ts中Object的静态方法比如Object.keys() 得到的结果集是string[],并支持得到一个对象的的key序列,对于很多人来说就比较头痛了。
const person = {name:"John",age:13,sex:"M"}
const keys = Object.keys(person);
// 得到
const keys: string[]
// 期望
type keys = keyof typeof person;
type keys = "name" | "age" | "sex"
如果需要以上的type-keys,则需要
const keys = Object.keys(person) as unknown as (keyof typeof person)[];
//得到
const keys: ("name" | "age" | "sex")[]
语法句子不是很复杂,我们逐句解析一下。
-
Object.keys(person)- 得到
string[]的结果集
- 得到
-
as unknown- 将上一个类型转化为未知类型
as是类型断言,断言的意思是:我认为它必然是 as 后面这种类型的。但是只能是以any或者unknown的类型或者向父类转型,否则不可用- 类型 "string" 到类型 "number" 的转换可能是错误的,因为两种类型不能充分重叠。如果这是有意的,请先将表达式转换为 "unknown"。
- 有一些小伙伴在开发时喜欢用any,就好比html全部使用div+span一样,这种做法不是很友好
-
keyof typeof persontypeof person获取一个实例的原始类型。keyof获取一个类型的key值(keyof typeof person)[]获得person类型的key值集合
正文
有了以上的基础经验,那我们就来设计右键菜单的定义吧。
菜单单项类型定义
// 单项
interface ContextItem<E> {
// 展示文本
label:string;
// 判定条件
condition?(args:any):boolean
// 回调事件
event:E;
// 传入参数列表
props?:string[]
// 分割线
split?:boolean
}
这里使用了一个泛型类型E,通常我们对泛型定义时,使用简短缩写字母,避免与现有的类型产生名称冲突
定义菜单的其他相关操作
菜单定义的模板文件
// template.menu.define.ts
/*菜单id*/
const id = ''
/*操作事件*/
const events = {} as const
/*展示项目*/
const items:ContextItem<keyof typeof events>[] = [];
到这里其实就差不多结束了。
使用以上模板构建一个用户的菜单配置项。
// person.menu.define.ts
/*菜单id*/
const id = 'Person'
/*操作事件*/
const events = {
setAge(person:Person,age:number){
person.age = age;
}
} as const
/*展示项目*/
const items:ContextItem<keyof typeof events>[] = [
{
label:"设置年龄",
// 定义event时,会从events的key里面提示对应的名称
event:'setAge',
props:['person','age'],
condition({age}){
return age == 0;
}
}
];
使用时,通过items生成菜单选项,点击后获取到被点击的ContextItem项目,使用 event 从events里获取事件,并且传递参数给到操作按钮
function menuClick(item:ContextItem<string>,argsProvider:()=>Record<string,any>){
const {
props,
event:eventName,
} = item
const callback = events[event];
const args = argsProvider();
const arguments = props.map(prop=>args[prop]);
callback.apply(null,arguments);
}
// 使用步骤
const person = {name:"John",age:13,sex:"M"}
// 提供给event的参数内容
const argsProvider = ()=> ({ person, age:11 })
// 被选中的单项
const item = items[0]
menuClick(item,argsProvider);
衍生需求,从一个菜单调用另一个菜单的操作
【从一个对象生成一个KV等值的新对象】 。
当菜单按钮需要对第三人提供操作时,为了代码友好性,则需要添加一层指令操作,让调用方可以快速获悉菜单的操作方式和方法。那么,则有以下ts的代码提示项
// 提供
const events = {
setAge(person:Person,age:number){
person.age = age;
}
} as const
// 期望
const eventSKV = { setAge: "nsetAgeame"}
// 操作过程
type EventKeys = keyof typeof eventSKV;
const eskv = Object
.keys(events)
.reduce((types, v: any) => (types[v] = v, types), {} as any) as
{ [key in EventKeys]: key }
// 得到
const eskv: {
setAge: "setAge";
}
如此使用eskv时,就可以得到setAge等等的提示了。
补充
- 如何获取一个函数的参数列表?
Parameters<typeof functionName>
- 如何获取一个函数的返回值类型?
ReturnType<typeof functionName>