一直没理解Decorator的作用,但最近在很多框架和库中看到它的身影,尤其是React和Redux,还有mobx中,所以专门查了一下资料,记录一下。
修饰器(Decorator)是一个函数,用来修改类的行为。不是很理解这种抽象概念,还是看代码讲解实际些。
//定义一个函数,也就是定义一个Decorator,target参数就是传进来的Class。
//这里是为类添加了一个静态属性
function testable(target) {
target.isTestable = true;
}
//在Decorator后面跟着Class,Decorator是函数的话,怎么不是testable(MyTestableClass)这样写呢?
//我只能这样理解:因为语法就这样,只要Decorator后面是Class,默认就已经把Class当成参数隐形传进Decorator了。
@testable
class MyTestableClass {}
console.log(MyTestableClass.isTestable) // true
但上面这样只传了一个Class作为参数不够灵活怎么办?
我们可以在外层套一个函数,只要最后返回的是一个Decorator即可,管你套多少个函数传多少个参数都无所谓。
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
//注意这里,隐形传入了Class,语法类似于testable(true)(MyTestableClass)
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
下面是修改类的prototype对象
function testable(target) {
target.prototype.isTestable = true;
}
@testable
class MyTestableClass {}
let obj = new MyTestableClass();
obj.isTestable // true
概念大概理解了,修饰器不仅可以修饰类,还可以修饰类的属性
//假如修饰类的属性则传入三个参数,对应Object.defineProperty()里三个参数,具体不细说
//target为目标对象,对应为Class的实例
//name为所要修饰的属性名,这里就是修饰器紧跟其后的name属性
//descriptor为该属性的描述对象
//这里的Decorator作用是使name属性不可写,并返回修改后的descriptor
function readonly(target, name, descriptor){
descriptor.writable = false;
return descriptor;
}
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
关于Object.defineProperty()可以看文章学习Object.defineProperty
再看一个复杂的例子
//定义一个Class并在其add上使用了修饰器
class Math {
@log
add(a, b) {
return a + b;
}
}
//定义一个修饰器
function log(target, name, descriptor) {
//这里是缓存旧的方法,也就是上面那个add()原始方法
var oldValue = descriptor.value;
//这里修改了方法,使其作用变成一个打印函数
//最后依旧返回旧的方法,真是巧妙
descriptor.value = function() {
console.log(`Calling "${name}" with`, arguments);
return oldValue.apply(null, arguments);
};
return descriptor;
}
const math = new Math();
math.add(2, 4);
看完上面的代码之后也大概了解装饰器是什么作用了,下面是看看怎么在React里使用。
我们都知道组件也是Class,但写React的时候怎么不见 new 语法?因为你应用组件的时候React隐性帮你实例化了。
这里展示一个例子用于切换React路由时,动态更改title属性的装饰器
//假如有这么一个页面组件,用于显示用户资料的,当从Home组件进去到这个组件时
//希望title从“Home Page”变成“Profile Page”
//注意这里隐形传入了组件,语法类似setTitle('Profile Page')(Profile)
@setTitle('Profile Page')
class Profile extends React.Component {
//....
}
//开始定义装饰器
//看到两个箭头函数感觉懵逼了,转化一个也就是一个函数里返回一个函数再返回一个组件包裹器而已
//title参数对应上面的“Profile Page”字符串
//WrappedComponent参数对应上面的Profile组件
//然后在组件加载完修改了title,在返回一个新组件,是不是很像高阶组件呢
const setTitle = (title) => (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
document.title = title
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
最后看看现在react-redux中怎么应用装饰器的
//定义一个组件
class Home extends React.Component {
//....
}
//以往从状态树取出对应的数据,让后通过props传给组件使用通过react-redux自带的connect()方法
export default connect(state => ({todos: state.todos}))(Home);
//使用装饰器的话就变成这样,好像没那么复杂
@connect(state => ({ todos: state.todos }))
class Home extends React.Component {
//....
}
参考文档:
http://es6.ruanyifeng.com/#docs/decorator
https://survivejs.com/react/appendices/understanding-decorators/
https://medium.com/@gigobyte/enhancing-react-components-with-decorators-441320e8606a
http://stackoverflow.com/questions/36553814/what-is-the-use-of-connect-decorator-in-react-redux