Decorator - 利用裝飾器武裝前端代碼

歷史html

  之前作後端時,接觸過一點Spring,也是第一次瞭解DI、IOC等概念,面向切面編程,對於面向對象編程還不怎麼熟練的狀況下,整我的慌的一批,它的日誌記錄、數據庫配置等都很是方便,不回侵入到業務代碼中,後來轉戰前端,就沒怎麼關注了.....前端

   

 

JS引入DI編程概念react

  學習 redux 時,看到語法裏面有 @ 符號,臥槽,後端已經侵入到前端啦,不知不覺中,前端已經這麼NB了,不再是寫寫頁面,用個框架,綁定個事件啦,已經把後端的一些經典設計思想融入進來了數據庫

  對於前端開發而言,若是有一種方式,可以將一些非業務代碼,甚至抽象的東西,無侵入的方式掛載到業務代碼上,那麼對於我的而言,這真是一種解放,太帥了......編程

 

裝飾器初探redux

 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校驗app

@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與mobxdom

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的方式進行編程,所以,作了一點東西,但願能夠給你們帶來一點啓發

 

 

 

 

 

 

 

    上面的裝飾器能夠掛載到 function、react的方法、mobx-stroe的action上,但若是有一個需求是這樣的,react中,想在進入頁面時進行埋點,上面的方法就不太適用了,由於在一個組件上掛載裝飾器,它能獲取到的上下文對象只是這個組件,既然能獲取到這個組件,那麼不妨HOC一下,高階組件一把

 

 

 

 

發現 高階組件的constructor 優先與原始組件的 constructor,同時componentDidMount反而晚於原始組件的componentDidMount,所以能夠這樣改,來根據需求進行埋點

   

 

 

 

 

原文出處:https://www.cnblogs.com/xfz1987/p/10310149.html

相關文章
相關標籤/搜索