zoukankan      html  css  js  c++  java
  • Decorator

    历史

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

       

    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 =/functions*w*(([sS]*?))/;
      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',  80}}>用户名:</span><input type="text" value={name} onChange={e => setName(e.target.value)}/><br/>
            <span style={{display:'inline-block',  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的方式进行编程,因此,做了一点东西,希望可以给大家带来一点启发

     

     

     

     

     

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

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

       

     

     

  • 相关阅读:
    vue2.X对接高德地图:vue-amap(一)
    RequestHeaders添加自定义参数
    git 拉取远程分支到本地
    git输错密码怎么办?
    webstorm最新版破解
    flex布局
    call和apply区别
    localStorage,sessionStorage,cookie
    详解单页面路由的几种实现原理(附demo)
    微信的踩坑之路----微信分享title和icon不显示
  • 原文地址:https://www.cnblogs.com/xfz1987/p/10310149.html
Copyright © 2011-2022 走看看