关键词 装饰器 Decorator 元编程
前言
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。装饰器使用 @expression 这种形式,expression 求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
本篇先从项目的宏观角度来总结一下 Decorator 如何组织。
我会持续分享一些知识整理,如果文章对您有帮助记得点赞鼓励一下哦????~,也可以通过邮件方式联系我文章列表:https://juejin.im/user/5bc8b9…
邮箱地址: 595506910@qq.com
目录
主要的 Decorator 依赖
vue-class-component
vuex-class
vue-property-decorator
core-decorators
自定义 Decorator 示例
哪些功能适合用 Decorator 实现
Decorator 实现小 Tips
See also
主要的 Decorator 依赖
vue-cli3 默认支持 Decorator, 年初重写了一个 design 库主要依赖官方和社区提供的 Decorator 来实现的组件。Decorator 可以非侵入的装饰类、方法、属性,解耦业务逻辑和辅助功能逻辑。以下是主要的三方 Decorator 组件,有了这些组件常用的 Vue 特性就可以全部转成 Decorator 风格了。
vue-class-component
@Component 如果您在声明组件时更喜欢基于类的 API,则可以使用官方维护的 vue-class-component 装饰器
实时计算 computed 属性, get computedMsg () {return ‘computed ‘ + this.msg}
生命周期钩子 mounted () {this.greet()}
vuex-class
让 Vuex 和 Vue 之间的绑定更清晰和可拓展
@State
@Getter
@Action
@Mutation
vue-property-decorator
这个组件完全依赖于 vue-class-component. 它具备以下几个属性:
@Component (完全继承于 vue-class-component)
@Prop:父子组件之间值的传递
@Emit:子组件向父组件传递
@Model:双向绑定
@Watch:观察的表达式变动
@Provice:在组件嵌套层级过深时。父组件不便于向子组件传递数据。就把数据通过 Provide 传递下去。
@Inject:然后子组件通过 Inject 来获取
Mixins (在 vue-class-component 中定义);
建议项目中只引用 vue-property-decorator 就可以了,避免 @Component 从 vue-class-component 和 vue-property-decorator 两个中随意引用。
core-decorators
@readonly
@autobind : TSX 回调函数中的 this,类的方法默认是不会绑定 this 的,可以使用 autobind 装饰器
@override
总结一下主要就分成这三类:
修饰类的:@Component、@autobind;
修饰方法的:@Emit、@Watch、@readonly、@override;
修饰属性的:@Prop、@readonly;
以上引用方法等详系内容可查看官方文档。要想完整的发挥 Decorator 的价值就需要根据需要自定义一些装饰器。下面自定义部分就来实现一个记录日志功能的装饰器。
自定义 Decorator 示例
@Logger,1.Logger 日志装饰器通常是修饰方法,Decorater 则是在运行时就被触发了,日志记录是在方法被调用时触发, 示例中通过自动发布事件实现调用时触发。2. 为增加日志记录的灵活性,需要通过暴露钩子函数的方式来改变日志记录的内容。
期望的日志格式
{
“logId”:””, // 事件 Id
“input”:””, // 方法输入的内容
“output”:””, // 方法输出的内容
“custom”:”” // 自定义的日志内容
}
实现
export function Logger(logId?: string, hander?: Function) {
const loggerInfo =Object.seal({logId:logId, input:”,output:”, custom: ”});
const channelName = ‘__logger’;
const msgChannel = postal.channel(channelName);
msgChannel.subscribe(logId, logData => {
// 根据业务逻辑来处理日志
console.log(logData);
});
return function (target: any,
key: string,
descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> {
const oldValue = descriptor.value
descriptor.value = function () {
const args: Array<any> = [];
for (let index in arguments) {
args.push(arguments[index]);
}
loggerInfo.input = `${key}(${args.join(‘,’)})`;
// 执行原方法
const value = oldValue.apply(this, arguments);
loggerInfo.output = value;
hander && (loggerInfo.custom = hander(loggerInfo.input, loggerInfo.output) || ”);
// 被调用时,会自动发出一个事件
msgChannel.publish(logId, loggerInfo);
}
return descriptor
}
}
使用
// 直接使用非常简洁
@Logger(‘event_get_detial1’)
getDetial(id?: string, category?: string) {
return “ 详细内容 ”;
}
// 或者使用自定义,让日志和业务逻辑分离
@Logger(‘event_get_detial2’, (input, output) => {
return ‘ 我是自定义内容 ’;
})
getDetial2(id?: string, category?: string) {
return “ 详细内容 ”;
}
…
<button @click=”getDetial2(‘1000’, ‘a’)”> 获取详情 </button>
效果: {logId: “event_get_detial2”, input: “getDetial2(1000,a)”, output: “ 详细内容 ”, custom: “ 我是自定义内容 ”}, 每次点击按钮都会触发一次。
TODO: 这里还需要对输入参数和输出参数中的引用数据类型做处理。
同时还需要掌握:装饰器工厂、装饰器组合、装饰器求值、参数装饰器、元数据
哪些功能适合用 Decorator 实现
官网和社区提供的这些 Decorator, 可以作为自己框架的底层设计。
日志功能全局都得用,调用方法基本一致,是最适合使用装饰器来实现,并且每个项目的日志记录各有差异,最适合自定义这部分。
Decorator 实现小 Tips
考虑下各类 Decorator 叠加和共存的问题, 可以参考官网关于装饰器组合描述
Decorator 的目标是在原有功能基础上,添加功能,切忌覆盖原有功能
类装饰器不能用在声明文件中 (.d.ts),也不能用在任何外部上下文中(比如 declare 的类)
装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。类是不会提升的,所以就没有这方面的问题。
注意迁移速度、避免一口吃成胖子的做法
不要另起炉灶对主流库创建 Decorator 库,主流库维护成本很高还是得有官方来维护,为保证质量不使用个人编写的 Decorator 库。自己在创建 Decorator 库时也要有这个意识,仅做一些有必要自定义的。
Decorator 不是管道模式,decorator 之间不存在交互,所以必须注意保持 decorator 独立性、透明性
Decorator 更适用于非业务功能需求
确定 decorator 的用途后,切记执行判断参数类型
decorator 针对每个装饰目标,仅执行一次
See also
ts 官网装饰器说明【深入阅读】https://www.tslang.cn/docs/ha…
Decorator 作用在不同的声明类型 target、key 说明 https://segmentfault.com/a/11…
JS 装饰器(Decorator)场景实战 https://zhuanlan.zhihu.com/p/…
Decorators in ES7 https://zhuanlan.zhihu.com/p/…
vue 组件 风格指南 https://cn.vuejs.org/v2/style…
使用 Typescript 封装一款装饰器风格的 Web 框架 https://zhuanlan.zhihu.com/p/…
Axios 装饰器实现 1.https://github.com/glangzh/re… 2.https://www.npmjs.com/package…
声明计算属性用的。https://my.oschina.net/lpcysz…
autobind https://blog.csdn.net/liubigg…
vue-class-component 源码阅读 https://www.jianshu.com/p/cfe…
函数和方法的区别 https://www.imooc.com/article…
很多人整理过使用方法 https://www.cnblogs.com/navys…
举个例子 join(separator?: string): string;
vue 源码解析系列-compute 实现机制 https://segmentfault.com/a/11…