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

来源:https://www.cnblogs.com/xfz1987/archive/2019/01/23/10310149.html
-Advertisement-
Play Games

歷史 以前做後端時,接觸過一點Spring,也是第一次瞭解DI、IOC等概念,面向切麵編程,對於面向對象編程還不怎麼熟練的情況下,整個人慌的一批,它的日誌記錄、資料庫配置等都非常方便,不回侵入到業務代碼中,後來轉戰前端,就沒怎麼關註了..... JS引入DI編程概念 學習 redux 時,看到語法里 ...


歷史

  以前做後端時,接觸過一點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 =/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,因此可以這樣改,來根據需求進行埋點

   

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • INSERT INTO SELECT語句 語句形式為:Insert into Table2(field1,field2,...) select value1,value2,... from Table1 或者:Insert into Table2 select * from Table1 註意:(1 ...
  • 結果: ...
  • 成功安裝Oracle 11g資料庫後,你會發現自己電腦運行速度會變慢,配置較低的電腦甚至出現非常卡的狀況,通過禁止非必須開啟的Oracle服務可以提升電腦的運行速度。那麼,具體該怎麼做呢? 按照win7 64位環境下Oracle 11g R2安裝詳解中的方法成功安裝Oracle 11g後,共有7個服 ...
  • 一、簡單的音頻播放 【項目準備】 ①一個視頻文件,視頻文件的位置 >在res下新建文件夾row >將視頻放入row文件夾中 ②一般音頻播放是不需要一直停留在界面的,所以音頻播放應該放在service中,即使界面被回收,也一直在播放。 【項目結構】 【界面代碼】 【MainActivity.class ...
  • JQ :even選擇器代表著選擇偶數行 JQ :odd 代表選擇奇數行 ...
  • 項目組織結構 ajax數據請求的封裝和api介面的模塊化管理 第三方庫按需載入 利用less的深度選擇器優雅覆蓋當前頁面UI庫組件的樣式 webpack實時打包進度 vue組件中選項的順序 路由的懶載入 路由模塊拆分化管理 項目組織結構 清晰的項目結構能讓別人開發進來更容易理解,當然,每個人都有一定 ...
  • 一、Vue渲染數據原理 原生JS改變頁面數據,必須要獲取頁面節點,也即是進行DOM操作,jQuery之類的框架只是簡化DOM操作的寫法,實質並沒有改變操作頁面數據的底層原理,DOM操作影響性能(導致瀏覽器的重繪和迴流),Vue是一個mvvm框架(庫),大幅度減少了DOM操作,操作數據如下圖: Vie ...
  • bable只轉換新語法 不支持新的全局變數如promise async等等,可以使用babel-polyfilll來相容 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...