在日常开发中,大部分人应该或多或许都使用到了es6语法了。而一些跑得快的人已经熟练的使用es7/8/9语法了。而装饰器就是其中一个。
装饰器语法大概长下面这样,主要分为类装饰器,属性装饰器:
@classDecorator // 类装饰器
class Classname extends BaseClass {
@propDecorator // 属性装饰器
properties = 'value str';
@funcDecorator // 方法装饰器
testFunction(value) {
console.log(value);
}
}
装饰器语法在我日常工作中也是比较常见的,例如mobx的@observable
, @computed
, @inject
,cocos Creator的@cclass
等。
装饰器本身的作用就是改变类的行为,所以只能在类上使用,同时注意这个改变装饰对象的行为发生在代码编译阶段,而不是运行阶段,也就是说编译器是在代码编译阶段(浏览器的编译阶段,而不是打包时的编译阶段)运行的。
/class-decorator.js
import React from 'react';
export default function RedBorder(Target) { // Target就是被装饰目标
return () => (<div style={{border: '1px solid red'}}>
<Target />
</div>);
}
/main.js
import React, { Component } from 'react';
import RedBorder from './class-decorator';
@RedBorder
class Index extends Component {
render() {
return (<div className="index">this is page index.</div>);
}
}
export default Index;
效果如上图所示。
在上例中类装饰器的作用很像一个高阶函数,可以使它返回一个新的组件,当然也可以返回传入的组件,而直接修改原组件。
例如:
import React from 'react';
export default function RedBorder(Target) {
Target.prototype.componentDidMount = () => {
console.log('componentDidMount.');
};
return Target;
}
使用类装饰器可以将与类组件的功能无关的业务拆分为独立的逻辑,在通过简单的引用修改类的行为,达到业务拆分的能力。
这种非常棒的代码逻辑拆分应用场景是很多的。比如“打点,懒加载,注入数据,错误捕获”。
类装饰器往往可以拆分成属性装饰器从而达到更加精确的功能拆分。
/func-decorator.js
export default function FuncDecorator(target, name, descriptor) {
const oldFunc = descriptor.value;
descriptor.value = function() {
oldFunc.call(target, '方法装饰器修改了这个方法');
};
return descriptor;
}
/prop-decoratpr.js
export default function PropDecorator(target, name, descriptor) {
const { configurable, enumerable } = descriptor;
let originalValue = descriptor.initializer().str;
Object.defineProperty(target, name, {
configurable,
enumerable,
get() {
return {value: 'get:' + originalValue};
},
set(val) {
originalValue = val;
}
});
return target[name];
}
/main.js
import React, { Component } from 'react';
import RedBorder from './class-decorator';
import FuncDecorator from './func-decorator';
import PropDecorator from './prop-decorator';
@RedBorder
class Index extends Component {
@FuncDecorator
testFunction(value) {
console.log(value); // '方法装饰器修改了这个方法'
}
@PropDecorator
name = '张三';
componentDidMount() {
console.log(this.name); // 'get: 张三'
}
render() {
return (<div className="index">this is page index.</div>);
}
}
export default Index;
属性装饰器本质与类装饰器差不多,不同的在于它多了两个参数(其实装饰器本质上就是Object.defineProperty的语法糖)。
-
第一个参数target表示装饰的方法附着于的类的原型对象。
-
第二个参数name表示修饰的方法的方法名。
-
第三个对象是一个描述对象,是对应的需要被修饰的方法的属性描述对象,了解Object.defineProperty的同学应该会更加熟悉,它与其第三个参数是一致的,都是可以分为数据描述符与存取描述符。
- 数据描述符: 包括value, writable。
- 存取描述符: 包括get, set方法。
- 其二者都包括enumerable, configurable。
其实如果装饰器用得好,日常工作中的功能就能很细粒度的拆分,让整个代码结构看上去更加简洁工整,但是有利就有弊,大量的使用装饰器会使得代码难以读懂,在一个组件中将功能通过装饰器组合的话,如果需要查某个功能点的实现还需要去查装饰器,或许还不指一层的装饰器,对于接手的同事就是一场灾难。
并且装饰器会改变类的行为,但是类的行为又是开发者设计的,强行更改其行为可能会导致未知的错误,所以最好装饰器如果能通过增加功能的手段实现就不要修改类的原型。