React Native面向切面编程

1,621 阅读6分钟

React Native:面向切面编程 && JavaScript ES7修饰符

编译期类加载期运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。 AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。 应用:将通用行的代码,在不污染其他功能代码的前提下,更好的复用

ES7修饰器

参考文档1:读懂ES7中javascript修饰器 参考文档2:JavaScript 修饰符是什么及何时使用它们
参考文档3:ECMAScript 6 入门

什么是修饰器

这个概念你以前可能听说过,就是“功能组合”,或者“高阶函数”。修饰器(Decorator)是ES7的一个提案,它的出现能解决两个问题:

  • 不同类间共享方法
  • 编译期对类和方法的行为进行改变

怎么使用 JavaScript 修饰符?

修饰符使用一个在 ES2017 中定义的特殊语法,在被修饰的代码前放置一个 @ 开头的符号。

为什么使用修饰符?

JavaScript 中已经可以实现功能组合,但明显比较困难 —— 甚至不可能 —— 把相同的技术应用到其它代码上(比如类和类属性)。

ES2017 草案添加了支持类和属性的修饰符,它可以用来解决这些问题,将来的 JavaScript 版本可能会允许在其它棘手的代码区域添加修饰符。

修饰符的不同类型

目前,唯一支持的修饰类型是用在类和类成员上的,包括属性、方法、getters 和 setters。

修饰符只不过是返回另一个函数的函数,这被称为被修饰项适当的细节。这些修饰符函数会在程序首次运行时被执行一次,而其返回值会替代被修饰的代码。

类成员修饰符

类修饰符

例子1:修饰类

@setProp
class User {}
function setProp(target) {
    target.age = 30
}
console.log(User.age)

这个例子要表达的是对User类使用setProp这个方法进行修饰,用来增加User类中age的属性,setProp方法会接收3个参数,我们现在接触第一个,target代表User类本身。

例子2:修饰类(自定义参数值)

@setProp(20)
class User {}
function setProp(value) {
    return function (target) {
        target.age = value
    }
}
console.log(User.age)

此例和上面功能基本一致,唯一差别在于值是参考修饰函数传过来的

例子2:修饰方法

class User {
    @readonly
    getName() {
        return 'Hello World'
    }
}

// readonly修饰函数,对方法进行只读操作
function readonly(target, name, descriptor) {
    descriptor.writable = false
    return descriptor
}

let u = new User()
// 尝试修改函数,在控制台会报错
u.getName = () => {
    return 'I will override'
}

上例中,我们对User类中的getName方法使用readonly修饰器进行修饰,使得方法不能被修改。第一个参数我们已经知道了,参数name为方法名,也就是readonly,参数descriptor是个啥东西呢,看到这行descriptor.writable = false,我们大家猜的也差不多了,这三个参数对应的就是Object.defineProperty的三个参数,我们来看一下:

我们设置descriptor.writable = false就是让函数不可以被修改,如果我们写成descriptor.value = 'function (){ console.log('Hello decorator') }'那么,输出就是Hello World了,而是Hello decorator,是不是已经意识到修饰器的好处了。现在我们来看看实际工作中,我们用到修饰器的例子

实际应用1:日志管理

我们经常在每一步打印一些日志文件,比如这步都干了些什么事,很明显打印日志的操作和业务代码根本就一点关系没有,我们不应该把日志和业务掺和在一起,这样使用修饰器就是避免这个问题,以下为代码:

class Pack {
    @log('读取package.json文件')
    step1() {
        // do something...
        // 没有修饰器之前,我们通常把console.log放到这里写
        // 放到函数里面写会有两个坏处
        //     1.console和业务无关,会破坏函数单一性原则
        //     2.如果要删除所有的console,那我们只能深入到每一个方法中
    }
    @log('合并webpack配置文件')
    step2() {
        // do something...
    }
}

function log(value) {
    return function (target, name, descriptor) {
        // 在这里,我们还可以拿到函数的参数,打印更加详细的信息
        console.log(value)
    }
}

let pack = new Pack()
pack.step1()
pack.step2()

实际应用2:检查登录

在实际的开发中常用得到,我们一些操作前,必须得判断用户是否登录,比较点赞、结算、发送弹幕...按照之前的写法,我们必须在每一个方法中判断用户的登录情况,然后再进行业务的操作,很显然前置条件和业务又混到了一起,用修饰器,就可以完美的解决这一问题,代码如下:

class User {
    // 获取已登录用户的用户信息
    @checkLogin
    getUserInfo() {
        /**
         * 之前,我们都会这么写:
         *      if(checkLogin()) {
         *          // 业务代码
         *      }
         *  这段代码会在每一个需要登录的方法中执行
         *  还是上面的问题,执行的前提和业务又混到了一起
         */
        console.log('获取已登录用户的用户信息')
    }
    // 发送消息
    @checkLogin
    sendMsg() {
        console.log('发送消息')
    }
}

// 检查用户是否登录,如果没有登录,就跳转到登录页面
function checkLogin(target, name, descriptor) {
    let method = descriptor.value

    // 模拟判断条件
    let isLogin = true

    descriptor.value = function (...args) {
        if (isLogin) {
            method.apply(this, args)
        } else {
            console.log('没有登录,即将跳转到登录页面...')
        }
    }
}
let u = new User()
u.getUserInfo()
u.sendMsg()

实际应用3:定时器

普通写法:

setTimeout(() => {
  this.fn();
}, 0);

修饰符写法:

@timeout(1000)
fn() {
  // doing
}

this.fn();

对应的 timeout 修饰器代码:

// timeout.ts
export function timeout(milliseconds: number = 0) {
  return function(target, key, descriptor) {
    // value 值相当于上面示例中 `change` 方法。
    var orgMethod = descriptor.value;
    descriptor.value = function(...args) {
      setTimeout(() => {
        orgMethod.apply(this, args);
      }, milliseconds);
    };
    return descriptor;
  }
}
  • target:实例对象,即 IndexComponent 实例化对象。
  • key:方法名称,即 fn。
  • descriptor:对象描述,同Object.getOwnPropertyDescriptor() 。

实际应用4:Core 修饰符

有一个神奇的库,称为 Core Decorators,它提供一些平常有用的修饰符。

这些修饰符使用简洁的语法,提供了非常有用的通用功能(比如,调用方法的时候,否决警告,允许某个值只读等)。

实际应用5:React

React 库很好的利用了高阶组件(Higher-Order Components)的概念。React 组件可以简单的写成函数,而它可以包含另一个组件。

实际应用6:MobX

MobX 库广泛使用了修饰符,让你很容易把字段标记为 Observable(可观察对象) 或 Computed(计算属性),以及把类变成 Observers(观察者)。

结语: 只要我们涉及需要在执行前做一些处理的应用,不管是修改函数的参数值,还是增加属性,还是执行的先决条件,我们都可以使用修饰器,这种编程的方式,就是面对切面编程