一、装饰器

1、什么是装饰器

  可以先看之前一篇博客:ES6里的修饰器Decorator

  Decorator 是 ES7 的一个新语法,他可以对一些对象进行装饰包装,然后返回一个被包装过的对象。

  可以装饰的对象包括:类,属性,方法等。打个比方:你出去玩,出门前戴了一顶帽子,这是帽子就是装饰器,你自己就是被装饰的对象。

2、装饰器的作用

  装饰器的作用就是为已经存在的函数或对象添加额外的功能。

  装饰器应用场景及理解: 装饰器本质上是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

3、类装饰器

  这里主要介绍一下类装饰器,使用类装饰器可以减少一些代码的重复编写。此时装饰器看起来更像是一个父类,但它又不是一个父类,因为被装饰的类重写一些生命周期函数的时候,装饰器里面的生命周期函数并不会被覆盖执行。

  对于componentDidMount来说,先执行被装饰类的componentDidMount,再执行装饰器内的componentDidMount;

  对于componentWillUnmount来讲,先执行装饰器的componentWillUnmount,再执行被装饰的类的componentWillUnmoun。

二、代码示例

  修饰器(Decorator)是一个函数,用来修改类的行为。不是很理解这种抽象概念,还是看代码讲解实际些。

1、修饰类

//定义一个函数,也就是定义一个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

  我觉得就是为了参考 Java 注解的写法吧,哈哈

  但上面这样只传了一个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

2、修饰属性

  概念大概理解了,修饰器不仅可以修饰类,还可以修饰类的属性

//假如修饰类的属性则传入三个参数,对应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}` }
}

  再看一个复杂的例子

//定义一个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中的应用

  看完上面的代码之后也大概了解装饰器是什么作用了,下面是看看怎么在React里使用。我们都知道组件也是Class,但写React的时候怎么不见 new 语法?因为你应用组件的时候React隐性帮你实例化了。

1、为所有被装饰的页面设置统一的背景色或自定义颜色
/*  装饰器  PageDecorator*/
import React, { Component } from 'react'
export const PageDecorator = param=> WrappedComponent=>{
    return class PageNormal extends Component{
        render(){
            let bgColor = param && param.background ? param.background : 'rgb(244,245,250)'
            return (
                <div style={{backgroundColor:bgColor}}>
                    <WrappedComponent  {...this.props} ></WrappedComponent>
                </div>
            )
     }
}
/*  被装饰页面*/
import React, { Component } from 'react'
import {PageDecorator} from '....'  // '...' 内是你的文件路径
@PageDecorator({background:'red'})
export default  class Page extends Component {
    ......
}
2、这里展示一个例子用于切换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} />
      }
   }
}

3、最后看看现在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 {
    //....
}

  此处只是一些简单的例子,实际应用中装饰器所做的不止是这些,我们可以抽象出某些页面共同的特点,使用装饰器创建公共的模版,其他被装饰的页面只要实现自己独特的部分就可以了。