We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
原创文章汇总:github/Nealyang
正在着手写 THE LAST TIME 系列的 Typescript 篇,而Decorator 一直是我个人看来一个非常不错的切面方案。所谓的切面方案就是我们常说的切面编程 AOP。一种编程思想,简单直白的解释就是,一种在运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是 AOP。AOP 和我们熟悉的 OOP 一样**,只是一个编程范式**,AOP 没有说什么规定要使用什么代码协议,必须要用什么方式去实现,这只是一个范式。而 Decorator 也就是AOP 的一种形式。
Typescript
Decorator
AOP
OOP
而本文重点不在于讨论编程范式,主要介绍 Typescript+Decorator 下图的一些知识讲解,其中包括最近笔者在写项目的一些应用。
貌似在去年的时候在公众号:【全栈前端精选】中,有分享过关于 Decorator 的基本介绍:Decorator 从原理到实战,里面有对 Decorator 非常详细的介绍。
本质上,它也就是个函数的语法糖。
Decorator 是 ES7 添加的性特性,当然,在 Typescript 很早就有了。早在此之前,就有提出与 Decorator 思想非常相近的设计模式:装饰者模式。
ES7
上图的WeaponAccessory就是一个Decorator,他们添加额外的功能到基类上。让其能够满足你的需求。
WeaponAccessory
简单的理解 Decorator,可以认为它是一种包装,对 对象,方法,属性的包装。就像 Decorator 侠,一身盔甲,只是装饰,以满足需求,未改变是人类的本质。
为什么要使用 Decorator,其实就是介绍到 AOP 范式的最大特点了:非侵入式增强。
比如笔者正在写的一个页面容器,交 PageContainer.tsx,基本功能包括滚动、autoCell、事件注入与解绑、placeHolder Container 的添加等基本功能。
PageContainer.tsx
autoCell
placeHolder Container
class PageContainer extends Components{ xxx }
这时候我正使用这个容器,想接入微信分享功能。或者错误兜底功能。但是使用这个容器的人非常多。分享不一定都是微信分享、错误兜底不一定都是张着我想要的样子。所以我必定要对容器进行改造和增强。
从功能点划分,这些的确属于容器的能力。所以在无侵入式的增强方案中,装饰者模式是一个非常好的选择。也就是话落到我们所说的 Decorator。(对于 React 或者 Rax,HOC 也是一种很好的方案,当然,其思想是一致的。)
React
Rax
HOC
+ @withError + @withWxShare class PageContainer extends Components{ xxx }
我们添加 Decorator,这样的做法,对原有代码毫无入侵性,这就是AOP的好处了,把和主业务无关的事情,放到代码外面去做。
JavaScript 毋庸置疑是一门非常好的语言,但是其也有很多的弊端,其中不乏是作者设计之处留下的一些 “bug”。当然,瑕不掩瑜~
JavaScript
话说回来,JavaScript 毕竟是一门弱类型语言,与强类型语言相比,其最大的编程陋习就是可能会造成我们类型思维的缺失(高级词汇,我从极客时间学到的)。而思维方式决定了编程习惯,编程习惯奠定了编程质量,工程质量划定了能力边界,而学习 Typescript,最重要的就是我们类型思维的重塑。
那么其实,Typescript 在我个人理解,并不能算是一个编程语言,它只是 JavaScript 的一层壳。当然,我们完全可以将它作为一门语言去学习。网上有很多推荐 or 不推荐 Typescript 之类的文章这里我们不做任何讨论,学与不学,用于不用,利与弊。各自拿捏~
再说说 typescript,其实对于 ts 相比大家已经不陌生了。更多关于 ts 入门文章和文档也是已经烂大街了。此文不去翻译或者搬运各种 api或者教程章节。只是总结罗列和解惑,笔者在学习 ts 过程中曾疑惑的地方。道不到的地方,欢迎大家评论区积极讨论。
typescript
ts
首先推荐下各自 ts 的编译环境:typescriptlang.org
再推荐笔者收藏的两个网站:
interface TypedPropertyDescriptor<T> { enumerable?: boolean; configurable?: boolean; writable?: boolean; value?: T; get?: () => T; set?: (value: T) => void; } declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void; declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
如上是 ClassDecorator、PropertyDecorator以及 MethodDecorator 的三个类型签名。
ClassDecorator
PropertyDecorator
MethodDecorator
由于 Decorator 在 Typescript 中还是一项实验性的给予支持,所以在 ts 的配置配置文件中,我们指明编译器对 Decorator 的支持。
在命令行或tsconfig.json里启用experimentalDecorators编译器选项:
tsconfig.json
experimentalDecorators
tsc --target ES5 --experimentalDecorators
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
在 Typescript 中,Decorator 可以修饰五种语句:类、属性、方法、访问器和方法参数。
类装饰器应用于构造函数之上,会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
注意,在 Typescript 中的class 关键字只是 JavaScript 构造函数的一个语法糖。由于类装饰器的参数是一个构造函数,其也应该返回一个构造函数。
class
我们先看一下官网的例子:
function classDecorator<T extends { new (...args: any[]): {} }>( constructor: T ) { return class extends constructor { newProperty = "new property"; hello = "override"; }; } @classDecorator class Greeter { property = "property"; hello: string; constructor(m: string) { this.hello = m; } } const greeter: Greeter = new Greeter("world"); console.log({ greeter }, greeter.hello);
{ new (...args: any[]): {} }表示一个构造函数,为了看起来清晰一些,我们也可以将其声明到外面:
{ new (...args: any[]): {} }
/** *构造函数类型 * * @export * @interface Constructable */ export interface IConstructable { new (...args:any[]):any }
属性装饰器有两个参数:
descriptor不会做为参数传入属性装饰器,这与TypeScript是如何初始化属性装饰器的有关。 因为目前没有办法在定义一个原型对象的成员时描述一个实例属性,并且没办法监视或修改一个属性的初始化方法。返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性。
function setDefaultValue(target: Object, propertyName: string) { target[propertyName] = "Nealayng"; } class Person { @setDefaultValue name: string; } console.log(new Person().name); // 输出: Nealayng
将上面的代码修改一下,我们给静态成员添加一个 Decorator
function setDefaultValue(target: Object, propertyName: string) { console.log(target === Person); target[propertyName] = "Nealayng"; } class Person { @setDefaultValue static displayName = 'PersonClass' name: string; constructor(name:string){ this.name = name; } } console.log(Person.prototype); console.log(new Person('全栈前端精选').name); // 输出: 全栈前端精选 console.log(Person.displayName); // 输出: Nealayng
以此可以验证,上面我们说的: Decorator 的第一个参数,对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
descriptor
注意: 如果代码输出目标版本小于ES5,descriptor将会是undefined。
function log( target: Object, propertyName: string, descriptor: TypedPropertyDescriptor<(...args: any[]) => any> ) { const method = descriptor.value; descriptor.value = function(...args: any[]) { // 将参数转为字符串 const params: string = args.map(a => JSON.stringify(a)).join(); const result = method!.apply(this, args); // 将结果转为字符串 const resultString: string = JSON.stringify(result); console.log(`Call:${propertyName}(${params}) => ${resultString}`); return result; }; } class Author { constructor(private firstName: string, private lastName: string) {} @log say(message: string): string { return `${message} by: ${this.lastName}${this.firstName}`; } } const author:Author = new Author('Yang','Neal'); author.say('《全站前端精选》');//Call:say("全站前端精选") => "全站前端精选 by: NealYang"
上述的代码比较简单,也就不做过多解释了。其中需要注意的是属性描述符 descriptor 的类型和许多文章写的类型有些不同:propertyDescriptor: PropertyDescriptor。
propertyDescriptor: PropertyDescriptor
从官方的声明文件可以看出,descriptor 设置为TypedPropertyDescriptor加上泛型约束感觉更加的严谨一些。
TypedPropertyDescriptor
当然,官网也是直接声明为类型PropertyDescriptor的。这个,仁者见仁。
PropertyDescriptor
访问器,不过是类声明中属性的读取访问器和写入访问器。访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
如果代码输出目标版本小于ES5,Property Descriptor将会是undefined。同时 TypeScript 不允许同时装饰一个成员的get和set访问器
function Enumerable( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { //make the method enumerable descriptor.enumerable = true; } class Person { _name: string; constructor(name: string) { this._name = name; } @Enumerable get name() { return this._name; } } console.log("-- creating instance --"); let person = new Person("Diana"); console.log("-- looping --"); for (let key in person) { console.log(key + " = " + person[key]); }
如果上面 get 不添加Enumerable的话,那么 for in 只能出来 _name _name = Diana
get
Enumerable
for in
_name
_name = Diana
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
参数装饰器只能用来监视一个方法的参数是否被传入。
在下面的示例中,我们将使用参数装饰器@notNull来注册目标参数以进行非空验证,但是由于仅在加载期间调用此装饰器(而不是在调用方法时),因此我们还需要方法装饰器@validate,它将拦截方法调用并执行所需的验证。
@notNull
@validate
function notNull(target: any, propertyKey: string, parameterIndex: number) { console.log("param decorator notNull function invoked "); Validator.registerNotNull(target, propertyKey, parameterIndex); } function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("method decorator validate function invoked "); let originalMethod = descriptor.value; //wrapping the original method descriptor.value = function (...args: any[]) {//wrapper function if (!Validator.performValidation(target, propertyKey, args)) { console.log("validation failed, method call aborted: " + propertyKey); return; } let result = originalMethod.apply(this, args); return result; } } class Validator { private static notNullValidatorMap: Map<any, Map<string, number[]>> = new Map(); //todo add more validator maps static registerNotNull(target: any, methodName: string, paramIndex: number): void { let paramMap: Map<string, number[]> = this.notNullValidatorMap.get(target); if (!paramMap) { paramMap = new Map(); this.notNullValidatorMap.set(target, paramMap); } let paramIndexes: number[] = paramMap.get(methodName); if (!paramIndexes) { paramIndexes = []; paramMap.set(methodName, paramIndexes); } paramIndexes.push(paramIndex); } static performValidation(target: any, methodName: string, paramValues: any[]): boolean { let notNullMethodMap: Map<string, number[]> = this.notNullValidatorMap.get(target); if (!notNullMethodMap) { return true; } let paramIndexes: number[] = notNullMethodMap.get(methodName); if (!paramIndexes) { return true; } let hasErrors: boolean = false; for (const [index, paramValue] of paramValues.entries()) { if (paramIndexes.indexOf(index) != -1) { if (!paramValue) { console.error("method param at index " + index + " cannot be null"); hasErrors = true; } } } return !hasErrors; } } class Task { @validate run(@notNull name: string): void { console.log("running task, name: " + name); } } console.log("-- creating instance --"); let task: Task = new Task(); console.log("-- calling Task#run(null) --"); task.run(null); console.log("----------------"); console.log("-- calling Task#run('test') --"); task.run("test");
对应的输出位:
param decorator notNull function invoked method decorator validate function invoked -- creating instance -- -- calling Task#run(null) -- method param at index 0 cannot be null validation failed, method call aborted: run ---------------- -- calling Task#run('test') -- running task, name: test
@validate装饰器把run方法包裹在一个函数里在调用原先的函数前验证函数参数.
run
装饰器工厂真的也就是一个噱头(造名词)而已,其实也是工厂的概念哈,毕竟官方也是这么号称的。在实际项目开发中,我们使用的也还是挺多的
**装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。**其实说白了,就是一个函数 return 一个 Decorator。非常像 JavaScript 函数柯里化,个人称之为“函数式Decorator”~
return
import { logClass } from './class-decorator'; import { logMethod } from './method-decorator'; import { logProperty } from './property-decorator'; import { logParameter } from './parameter-decorator'; // 装饰器工厂,根据传入的参数调用相应的装饰器 export function log(...args) { switch (args.length) { case 3: // 可能是方法装饰器或参数装饰器 // 如果第三个参数是数字,那么它是索引,所以这是参数装饰器 if typeof args[2] === "number") { return logParameter.apply(this, args); } return logMethod.apply(this, args); case 2: // 属性装饰器 return logProperty.apply(this, args); case 1: // 类装饰器 return logClass.apply(this, args); default: // 参数数目不合法 throw new Error('Not a valid decorator'); } } @log class Employee { @log private name: string; constructor(name: string) { this.name = name; } @log greet(@log message: string): string { return `${this.name} says: ${message}`; } }
一个类中,不同位置申明的装饰器,按照以下规定的顺序应用:
parameterDecorator
methodDecorator
classDecorator
propertyDecorator
function ClassDecorator() { return function (target) { console.log("I am class decorator"); } } function MethodDecorator() { return function (target, methodName: string, descriptor: PropertyDescriptor) { console.log("I am method decorator"); } } function Param1Decorator() { return function (target, methodName: string, paramIndex: number) { console.log("I am parameter1 decorator"); } } function Param2Decorator() { return function (target, methodName: string, paramIndex: number) { console.log("I am parameter2 decorator"); } } function PropertyDecorator() { return function (target, propertyName: string) { console.log("I am property decorator"); } } @ClassDecorator() class Hello { @PropertyDecorator() greeting: string; @MethodDecorator() greet( @Param1Decorator() p1: string, @Param2Decorator() p2: string) { } }
输出为:
I am parameter2 decorator I am parameter1 decorator I am method decorator I am property decorator I am class decorator
由于是业务代码,与技术无关琐碎,只截取部分代码示意,非 Decorator 代码,以截图形式
这应该也是整理这篇文章最开始的原因了。直接说说项目(rax1.0+Decorator)吧。
rax1.0
需求很简单,就是是编写一个页面的容器。
部分项目结构:
pm-detail ├─ constants │ └─ index.ts //常量 ├─ index.css ├─ index.tsx // 入口文件 └─ modules // 模块 └─ page-container // 容器组件 ├─ base //容器基础组件 ├─ decorator // 装饰器 ├─ index.tsx ├─ lib // 工具 └─ style.ts
重点看下如下几个文件
其实是基础功能的封装
在此基础上,我们需要个能滚动的容器
也是基于 Base.tsx 基础上,封装一些滚动容器具有的功能
Base.tsx
import is from './util/is'; import map from './util/map'; const isObject = is(Object); const isFunction = is(Function); class Style { static factory = (...args) => new Style(...args); analyze(styles, props, state) { return map(v => { if (isFunction(v)) { const r = v.call(this.component, props, state); return isObject(r) ? this.analyze(r, props, state) : r; } if (isObject(v)) return this.analyze(v, props, state); return v; })(styles); } generateStyles(props, state) { const { styles: customStyles } = props; const mergedStyles = this.analyze(this.defaultStyles, props, state); if (customStyles) { Object.keys(customStyles).forEach(key => { if (mergedStyles[key]) { if (isObject(mergedStyles[key])) { Object.assign(mergedStyles[key], customStyles[key]); } else { mergedStyles[key] = customStyles[key]; } } else { mergedStyles[key] = customStyles[key]; } }); } return { styles: mergedStyles, }; } constructor(defaultStyles = {}, { vary = true } = {}) { const manager = this; this.defaultStyles = defaultStyles; return BaseComponent => { const componentWillMount = BaseComponent.prototype.componentWillMount; const componentWillUpdate = BaseComponent.prototype.componentWillUpdate; BaseComponent.prototype.componentWillMount = function() { manager.component = this; Object.assign(this, manager.generateStyles(this.props, this.state)); return componentWillMount && componentWillMount.apply(this, arguments); }; if (vary) { BaseComponent.prototype.componentWillUpdate = function(nextProps, nextState) { Object.assign(this, manager.generateStyles(nextProps, nextState)); return componentWillUpdate && componentWillUpdate.apply(this, arguments); }; } return BaseComponent; }; } } export default Style.factory;
然后我们需要一个错误的兜底功能,但是这个本身应该不属于容器的功能。所以我们封装一个 errorDecorator
function withError<T extends IConstructable>(Wrapped: T) { const willReceiveProps = Wrapped.prototype.componentWillReceiveProps; const didMount = Wrapped.prototype.componentDidMount; const willUnmount = Wrapped.prototype.componentWillUnmount; return class extends Wrapped { static displayName: string = `WithError${getDisplayName(Wrapped)}·`; static defaultProps: IProps = { isOffline: false, isError: false, errorRefresh: () => { window.location.reload(true); } }; private state: StateType; private eventNamespace: string = ""; constructor(...args: any[]) { super(...args); const { isOffline, isError, errorRefresh, tabPanelIndex } = this.props; this.state = { isOffline, isError, errorRefresh }; if (tabPanelIndex > -1) { this.eventNamespace = `.${tabPanelIndex}`; } } triggerErrorHandler = e => {...}; componentWillReceiveProps(...args) { if (willReceiveProps) { willReceiveProps.apply(this, args); } const [nextProps] = args; const { isOffline, isError, errorRefresh } = nextProps; this.setState({ isOffline, isError, errorRefresh }); } componentDidMount(...args) { if (didMount) { didMount.apply(this, args); } const { eventNamespace } = this; emitter.on( EVENTS.TRIGGER_ERROR + eventNamespace, this.triggerErrorHandler ); } componentWillUnmount(...args) { if (willUnmount) { willUnmount.apply(this, args); } const { eventNamespace } = this; emitter.off( EVENTS.TRIGGER_ERROR + eventNamespace, this.triggerErrorHandler ); } render() { const { isOffline, isError, errorRefresh } = this.state; if (isOffline || isError) { let errorType = "system"; if (isOffline) { errorType = "offline"; } return <Error errorType={errorType} refresh={errorRefresh} />; } return super.render(); } }; }
然后我们进行整合导出
import { createElement, PureComponent, RaxNode } from 'rax'; import ScrollBase from "./base/scrollBase"; import withError from "./decorator/withError"; interface IScrollContainerProps { spmA:string; spmB:string; renderHeader?:()=>RaxNode; renderFooter?:()=>RaxNode; [key:string]:any; } @withError class ScrollContainer extends PureComponent<IScrollContainerProps,{}> { render() { return <ScrollBase {...this.props} />; } } export default ScrollContainer;
使用如下:
最后附一张,本文思维导图。
公众号回复:【xmind1】 获取思维导图源文件
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
正在着手写 THE LAST TIME 系列的
Typescript
篇,而Decorator
一直是我个人看来一个非常不错的切面方案。所谓的切面方案就是我们常说的切面编程AOP
。一种编程思想,简单直白的解释就是,一种在运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是 AOP。AOP
和我们熟悉的OOP
一样**,只是一个编程范式**,AOP
没有说什么规定要使用什么代码协议,必须要用什么方式去实现,这只是一个范式。而Decorator
也就是AOP
的一种形式。而本文重点不在于讨论编程范式,主要介绍
Typescript
+Decorator
下图的一些知识讲解,其中包括最近笔者在写项目的一些应用。介绍
什么是 Decorator
貌似在去年的时候在公众号:【全栈前端精选】中,有分享过关于
Decorator
的基本介绍:Decorator 从原理到实战,里面有对Decorator
非常详细的介绍。本质上,它也就是个函数的语法糖。
Decorator
是ES7
添加的性特性,当然,在Typescript
很早就有了。早在此之前,就有提出与Decorator
思想非常相近的设计模式:装饰者模式。上图的
WeaponAccessory
就是一个Decorator
,他们添加额外的功能到基类上。让其能够满足你的需求。简单的理解
Decorator
,可以认为它是一种包装,对 对象,方法,属性的包装。就像 Decorator 侠,一身盔甲,只是装饰,以满足需求,未改变是人类的本质。为什么要使用 Decorator
为什么要使用
Decorator
,其实就是介绍到AOP
范式的最大特点了:非侵入式增强。比如笔者正在写的一个页面容器,交
PageContainer.tsx
,基本功能包括滚动、autoCell
、事件注入与解绑、placeHolder Container
的添加等基本功能。这时候我正使用这个容器,想接入微信分享功能。或者错误兜底功能。但是使用这个容器的人非常多。分享不一定都是微信分享、错误兜底不一定都是张着我想要的样子。所以我必定要对容器进行改造和增强。
从功能点划分,这些的确属于容器的能力。所以在无侵入式的增强方案中,装饰者模式是一个非常好的选择。也就是话落到我们所说的
Decorator
。(对于React
或者Rax
,HOC
也是一种很好的方案,当然,其思想是一致的。)我们添加
Decorator
,这样的做法,对原有代码毫无入侵性,这就是AOP
的好处了,把和主业务无关的事情,放到代码外面去做。关于 Typescript
JavaScript
毋庸置疑是一门非常好的语言,但是其也有很多的弊端,其中不乏是作者设计之处留下的一些 “bug”。当然,瑕不掩瑜~话说回来,
JavaScript
毕竟是一门弱类型语言,与强类型语言相比,其最大的编程陋习就是可能会造成我们类型思维的缺失(高级词汇,我从极客时间学到的)。而思维方式决定了编程习惯,编程习惯奠定了编程质量,工程质量划定了能力边界,而学习Typescript
,最重要的就是我们类型思维的重塑。那么其实,
Typescript
在我个人理解,并不能算是一个编程语言,它只是JavaScript
的一层壳。当然,我们完全可以将它作为一门语言去学习。网上有很多推荐 or 不推荐Typescript
之类的文章这里我们不做任何讨论,学与不学,用于不用,利与弊。各自拿捏~再说说
typescript
,其实对于ts
相比大家已经不陌生了。更多关于ts
入门文章和文档也是已经烂大街了。此文不去翻译或者搬运各种 api或者教程章节。只是总结罗列和解惑,笔者在学习 ts 过程中曾疑惑的地方。道不到的地方,欢迎大家评论区积极讨论。首先推荐下各自 ts 的编译环境:typescriptlang.org
再推荐笔者收藏的两个网站:
Typescript 中的 Decorator 签名
如上是
ClassDecorator
、PropertyDecorator
以及MethodDecorator
的三个类型签名。基本配置
由于
Decorator
在Typescript
中还是一项实验性的给予支持,所以在ts
的配置配置文件中,我们指明编译器对Decorator
的支持。在命令行或
tsconfig.json
里启用experimentalDecorators
编译器选项:类型
在
Typescript
中,Decorator
可以修饰五种语句:类、属性、方法、访问器和方法参数。class definitions
类装饰器应用于构造函数之上,会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
注意,在
Typescript
中的class
关键字只是JavaScript
构造函数的一个语法糖。由于类装饰器的参数是一个构造函数,其也应该返回一个构造函数。我们先看一下官网的例子:
{ new (...args: any[]): {} }
表示一个构造函数,为了看起来清晰一些,我们也可以将其声明到外面:properties
属性装饰器有两个参数:
将上面的代码修改一下,我们给静态成员添加一个
Decorator
以此可以验证,上面我们说的: Decorator 的第一个参数,对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
methods
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
descriptor
。上述的代码比较简单,也就不做过多解释了。其中需要注意的是属性描述符
descriptor
的类型和许多文章写的类型有些不同:propertyDescriptor: PropertyDescriptor
。从官方的声明文件可以看出,descriptor 设置为
TypedPropertyDescriptor
加上泛型约束感觉更加的严谨一些。当然,官网也是直接声明为类型
PropertyDescriptor
的。这个,仁者见仁。accessors
访问器,不过是类声明中属性的读取访问器和写入访问器。访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
如果上面
get
不添加Enumerable
的话,那么for in
只能出来_name
_name = Diana
parameters
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
在下面的示例中,我们将使用参数装饰器
@notNull
来注册目标参数以进行非空验证,但是由于仅在加载期间调用此装饰器(而不是在调用方法时),因此我们还需要方法装饰器@validate
,它将拦截方法调用并执行所需的验证。对应的输出位:
@validate
装饰器把run
方法包裹在一个函数里在调用原先的函数前验证函数参数.装饰器工厂
装饰器工厂真的也就是一个噱头(造名词)而已,其实也是工厂的概念哈,毕竟官方也是这么号称的。在实际项目开发中,我们使用的也还是挺多的
**装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。**其实说白了,就是一个函数
return
一个Decorator
。非常像JavaScript
函数柯里化,个人称之为“函数式Decorator”~加载顺序
一个类中,不同位置申明的装饰器,按照以下规定的顺序应用:
parameterDecorator
)时,从最后一个参数依次向前执行methodDecorator
)和方法参数装饰器(parameterDecorator
)中,参数装饰器先执行classDecorator
)总是最后执行。methodDecorator
)和属性装饰器(propertyDecorator
),谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行。输出为:
实战
这应该也是整理这篇文章最开始的原因了。直接说说项目(
rax1.0
+Decorator
)吧。需求很简单,就是是编写一个页面的容器。
部分项目结构:
重点看下如下几个文件
其实是基础功能的封装
在此基础上,我们需要个能滚动的容器
也是基于
Base.tsx
基础上,封装一些滚动容器具有的功能然后我们需要一个错误的兜底功能,但是这个本身应该不属于容器的功能。所以我们封装一个 errorDecorator
然后我们进行整合导出
使用如下:
学习交流
最后附一张,本文思维导图。
参考文献
The text was updated successfully, but these errors were encountered: