欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Decorator - 利用装饰器武装前端代码

程序员文章站 2022-05-02 12:14:11
历史 以前做后端时,接触过一点Spring,也是第一次了解DI、IOC等概念,面向切面编程,对于面向对象编程还不怎么熟练的情况下,整个人慌的一批,它的日志记录、数据库配置等都非常方便,不回侵入到业务代码中,后来转战前端,就没怎么关注了..... JS引入DI编程概念 学习 redux 时,看到语法里 ......

历史

  以前做后端时,接触过一点spring,也是第一次了解di、ioc等概念,面向切面编程,对于面向对象编程还不怎么熟练的情况下,整个人慌的一批,它的日志记录、数据库配置等都非常方便,不回侵入到业务代码中,后来转战前端,就没怎么关注了.....

   Decorator - 利用装饰器武装前端代码

 

js引入di编程概念

  学习 redux 时,看到语法里面有 @ 符号,卧槽,后端已经侵入到前端啦,不知不觉中,前端已经这么nb了,再也不是写写页面,用个框架,绑定个事件啦,已经把后端的一些经典设计思想融入进来了

  对于前端开发而言,如果有一种方式,能够将一些非业务代码,甚至抽象的东西,无侵入的方式挂载到业务代码上,那么对于个人而言,这真是一种解放,太帅了......

 

装饰器初探

 1.给方法记录一下log

@log
class numberic {
  add(...nums) {
    return nums.reduce((p, n) => (p + n), 0)
  }
}

function log(target) {
  // numberic
  const desc = object.getownpropertydescriptors(target.prototype)
  /**
   * desc
      add:
        configurable: true  - 可配置
        enumerable: false   - 可枚举
        value: ƒ ()    
        writable: true      - 可改写
        __proto__: object

      constructor:
        configurable: true
        enumerable: false
        value: ƒ numberic()
        writable: true
        __proto__: object
   */
  

  for (const key of object.keys(desc)) {
    if (key === 'constructor') {
      continue
    }

    const func = desc[key].value

    if ('function' === typeof func) {
      object.defineproperty(target.prototype, key, {
        value(...args) {
          console.log('before ' + key)
          const ret = func.apply(this, args)
          console.log('after ' + key)
          return ret
        }
      })
    }
  }
}

new numberic().add(2)
// before add
// 2
// after add

 

2.给属性添加readonly校验

@log
class numberic {
  @readonly pi = 3.1415126
  
  add(...nums) {
    return nums.reduce((p, n) => (p + n), 0)
  }
}

function readonly(target, key, descriptor) {
  descriptor.writable = false
}

new numberic().pi = 100
// 报错

3.给一个表单提交进行校验

var validaterules = {
  expectnumber(value) {
    return object.prototype.tostring.call(value) === '[object number]'
  },
  maxlength(value) {
    return value <= 30
  }
}

function validate(value) {
  return object.keys(validaterules).every(key => validaterules[key](value))
}

function enablevalidate(target, key, descriptor) {
  const fn = descriptor.value
  if (typeof fn === 'function') {
    descriptor.value = function(value) {
      return validate(value)
        ? fn.apply(this, [value])
        : console.error('form validate failed!')
    }
  }
}

class form {
  @enablevalidate
  send(value) {
    console.log('this is send action', value)
  }
}

let form = new form()
form.send(44) // form validate failed!
form.send('12') // form validate failed!
form.send(12) // this is send action 12

 

应用react与mobx

import react, { component } from 'react'
import { render } from 'react-dom'
import { observable, action } from 'mobx'
import { observer } from 'mobx-react'

import { log, required, trackinout } from './decorator.js'

// store
@log
class user {
  @observable name = ''
  @observable password = ''

  @action setname = val => {
    this.name = val
  }

  @action setpwd = val => {
    this.password = val
  }

  @action login = (info) => {
    console.log('ready to login', info.name, info.password)
  }
}


const userstore = new user()

@observer
class login extends component {
  constructor(props){
    super(props)
    console.log('原始组件的constructor')
  }

  @required(['name', 'password'])
  login(info) {    
    this.props.store.login(info)
  }

  componentdidmount() {
    console.log('原始组件的cmd')
  }

  render() {
    let { name, password, setname, setpwd } = this.props.store
    return (
      <div classname="login-panel">
        <input type="text" value={name} onchange={e => setname(e.target.value)}/>
        <input type="password" value={password} onchange={e => setpwd(e.target.value)}/><br/>
        <button onclick={() => this.login({ name, password })}>登录</button>
      </div>
    )
  }
}

render(<login store={userstore} />, document.getelementbyid('root'))


import _ from 'lodash'
import react from 'react'

// 获取方法参数的名称列表
const getargumentslist = func => {
  var funcstring = func.tostring();
  var regexp =/function\s*\w*\(([\s\s]*?)\)/;
  if(regexp.test(funcstring)){
    var arglist = regexp.$1.split(',');
    return arglist.map(function(arg){
            return arg.replace(/\s/g,'');
          });
  }else{
    return []
  }
}

// 记录日志
export const log = target => {
  const desc = object.getownpropertydescriptors(target.prototype)
  for (const key of object.keys(desc)) {
    if (key === 'constructor') {
      continue
    }

    const func = desc[key].value

    if ('function' === typeof func) {
      object.defineproperty(target.prototype, key, {
        value(...args) {
          console.log(`before ${key}`)
          const ret = func.apply(this, args)
          console.log(`after ${key}`)
          return ret
        }
      })
    }
  }
}

// 只读
export const readonly = (target, key, descriptor) => {
  descriptor.writable = false
}

// 必传参数
export const required = checkarr => {
  return (target, key, descriptor) => {
    
    const fn = descriptor.value
    // console.log(target, key, descriptor) 
    if (typeof fn === 'function') {
      descriptor.value = function(args) {
        console.log('required')
        if (_.isplainobject(args)) {
          if (checkarr && checkarr.length > 0) {
            for (let a of checkarr) {
              if (!args[a]) {
                throw new error(`[required] params ${a} of ${key} is undefined or null!`)
              }
            }
          }
        } else if (_.isarray(args)) {
          if (args.length == 0) {
            throw new error(`[required] params ${getargumentslist(fn)[0]} of ${key} length is 0!`)
          }
        } else {
          if (_.isempty(args)) {
            throw new error(`[required] params ${getargumentslist(fn)[0]} of ${key} is undefined!`)
          }
        }
 
        fn.apply(this, [args])
      }
    }
    // console.log(target)
    // console.log(key)
    // console.log(descriptor)
    // console.log(checkarr)
  }
}

 

直接应用在mobx上

 

import react, { component } from 'react'
import { render } from 'react-dom'
import { observable, action, computed } from 'mobx'
import { observer } from 'mobx-react'

//custom 
import { log, required, track } from './decorator.js'

// store
@log
class user {
  @observable name = ''
  @observable password = ''

  @action setname = val => {
    this.name = val
  }

  @action setpwd = val => {
    this.password = val
  }
  
  @required(['name', 'password'])
  @track({ evt: '1', data: 'test', execute: 'after' })
  @action
  login(info) {
    // login 方法如果想要使用required,则不能使用箭头函数
    console.log('login', info.name, info.password)
  }
}

const userstore = new user()

@observer
class login extends component {
  render() {
    let { name, password, setname, setpwd } = this.props.store
    return (
      <div classname="login-panel">
        <span style={{display:'inline-block', width: 80}}>用户名:</span><input type="text" value={name} onchange={e => setname(e.target.value)}/><br/>
        <span style={{display:'inline-block', width: 80}}>密码:</span><input type="password" value={password} onchange={e => setpwd(e.target.value)}/><br/>
        <button onclick={() => this.props.store.login({ name, password })}>登录</button>
      </div>
    )
  }
}

render(<login store={userstore} />, document.getelementbyid('root'))

 

 

无侵入式埋点

最近在做系统的埋点,很多地方要加入埋点,尤其是在一些事件上,如果按照以前的思路,就得将大量的埋点代码侵入到业务代码上,维护上就有点费劲了,因此联想到es7的decorate 装饰器,可以ioc的方式进行编程,因此,做了一点东西,希望可以给大家带来一点启发

 

Decorator - 利用装饰器武装前端代码

 

Decorator - 利用装饰器武装前端代码

 

Decorator - 利用装饰器武装前端代码

 

 

Decorator - 利用装饰器武装前端代码

 

Decorator - 利用装饰器武装前端代码

 

    上面的装饰器可以挂载到 function、react的方法、mobx-stroe的action上,但如果有一个需求是这样的,react中,想在进入页面时进行埋点,上面的方法就不太适用了,因为在一个组件上挂载装饰器,它能获取到的上下文对象只是这个组件,既然能获取到这个组件,那么不妨hoc一下,高阶组件一把

Decorator - 利用装饰器武装前端代码

 

Decorator - 利用装饰器武装前端代码

 

 

 

发现 高阶组件的constructor 优先与原始组件的 constructor,同时componentdidmount反而晚于原始组件的componentdidmount,因此可以这样改,来根据需求进行埋点