TypeScript 注解(上)
引言
上次学习了一下typecirpt-ioc
项目,一个优秀的IOC
容器,那个项目中用到了TypeScript
注解,反正比我写的容器高级多了。是时候学习一下TypeScript
注解了。
探究
测试环境选择
在WebStorm
环境下建立一个示例项目,试了一下,报错。
Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option to remove this warning.
对装饰器的实验性支持是一个在未来版本中可能会发生变化的特性,请设置xxx
选项以移除该警告。
注解发展史
编译器所说的装饰器,其实就是我们所说的注解。看这个提示,应该是目前还不支持注解,还是在试验阶段。咦?那为什么Angular
启用了注解呢?
后来查到了这张图:
主流浏览器全面支持ES5
,所以我们无论用AngularJS
的原生JavaScript
开发也好,用Angular
、React
的TypeScript
开发也好,最后到浏览器执行的代码都是ES5
脚本。
我们应该认识下面三个圈,ES5
,ES6
涵盖了ES5
并扩充了类与模块,TypeScript
又是ES6
的超集。
最外围的AtScript
由Google AtScript
团队提出,对TypeScript
又进行了扩充,支持注解与introspection
(这个不知道是啥,百度翻译是“自我反省”,水平不够不敢随便翻译)。
Google AtScript
团队已经将注解
作为ES7
的提案。但是经过测试,ES7
环境下还不支持注解,可能是草案没有通过?
后来,Google AtScript
与Microsoft
合作:
We’re excited to announce that we have converged the TypeScript and AtScript languages, and that Angular 2, the next version of the popular JavaScript library for building web sites and web apps, will be developed with TypeScript.
我们很高兴:我们已经融合了TypeScript
和AtScript
,并且Angular 2
将采用TypeScript
来开发。
这就是我们的Angular
。同样是TypeScript
,我们可以像Spring
中一样去加注解,而不需要像React
一样去继承。
class ShoppingList extends React.Component { render() { return ( <div className="shopping-list"> <h1>Shopping List for {this.props.name}</h1> <ul> <li>Instagram</li> <li>WhatsApp</li> <li>Oculus</li> </ul> </div> ); } }
自定义注解
新建tsconfig.json
文件,将compilerOptions
中的experimentalDecorators
设置为true
,以忽略警告。
注解(装饰器)是一类特殊类型的声明,可以用在类、方法、构造器、属性和参数上。
其实本质上,定义一个注解,就是定义一个TypeScript
方法,只是该方法必须符合官方的规范。
注解工厂
定义两个方法hello
和world
方法,两个方法分别返回符合规范的函数闭包,参数target
、propertyKey
、descriptor
。经测试,这三个参数中target
和propertyKey
是必须的,没有的话编译过不去,descriptor
可以省略。
function hello() { console.log("hello(): 加载."); return function (target, propertyKey: string, descriptor: PropertyDescriptor) { console.log("hello(): 执行."); } } function world() { console.log("world(): 加载."); return function (target, propertyKey: string, descriptor: PropertyDescriptor) { console.log("world(): 执行."); } } class Main { @hello() @world() method() { console.log('method(): 执行.'); } }
编译、执行:
注解方法在编译期间执行。注解方法加载从上到下,闭包功能执行从下到上。
打印三个参数的结果,具体含义在下面的方法注解一栏中讲解。
类注解
类注解应用于类的构造函数,可以使用它去观察、修改或替换类的定义。类注解不能在声明文件中被使用,或其他ambient context
中使用。
class Person { message: string; constructor(message: string) { this.message = message; } greet() { console.log(`Hello ${this.message} !`); } } const person = new Person('World'); person.greet();
使用类注解,我们可以覆盖原来的构造函数,所以依赖注入可能就是用这种方式实现的。
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) { return class extends constructor { message = 'Decorator'; } } @classDecorator class Person { message: string; constructor(message: string) { this.message = message; } greet() { console.log(`Hello ${this.message} !`); } } const person = new Person('World'); person.greet();
覆盖掉了原来的构造函数。
方法注解
方法注解应用于方法的属性描述器,也可以观察、修改替换方法定义。方法注解不能在声明文件中被使用,或者是方法重载,或其他ambient context
中使用。
class Person { message: string; constructor(message: string) { this.message = message; } greet() { console.log(`Hello ${this.message} !`); } } const person = new Person('World'); for (const property in person) { console.log(property); }
for in person
对象,遍历出了message
和greet
。
function enumerable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.enumerable = value; }; } class Person { message: string; constructor(message: string) { this.message = message; } @enumerable(false) greet() { console.log(`Hello ${this.message} !`); } } const person = new Person('World'); for (const property in person) { console.log(property); }
添加方法注解,@enumerable
,将该方法设置为不可枚举。
遍历时就没有greet
了,恕我直言,我觉得这个属性描述器好像没什么用。
致歉
身体抱恙,头脑混乱,若有错误之处欢迎指出,还有一个利用反射实现的属性装饰器,以后再与大家详述。
总结
工程要的是经验,框架要的是功底。你喜欢哪一个?