引言
上次学习了一下 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 了,恕我直言,我觉得这个属性描述器好像没什么用。
致歉
身体抱恙,头脑混乱,若有错误之处欢迎指出,还有一个利用反射实现的属性装饰器,以后再与大家详述。
总结
工程要的是经验,框架要的是功底。你喜欢哪一个?