高階函數在組件的接口開發上的應用

寫這篇文章的想法,其實來自於本身的一次React組件開發實踐(傳送門)。當時是作一個markdown編輯器,原理是用戶編輯事後的string,通過其餘開源的markdown語法解析器解析後輸出html格式的文檔,這樣就能夠實時預覽markdown了。開發初版時,我將解析器markdown-it內置在組件裏,雖然可以知足了需求,可是也帶來了一些問題:解析器會打包到本身的組件裏致使代碼體積增大,用戶不能自定義解析器(ps:就想用那誰誰家的,咋地)。後來在項目貢獻者sylingd的改造下,將組件的renderHtml接口改形成了一個高階函數,完美的解決了這個問題(在此表示衷心的感謝!)html

高階函數

  1. 高階函數是一個函數,它接收函數做爲參數或將函數做爲輸出返回。常運用於AOP,函數柯里化,節流或防抖等。
  2. 函數式編程的思想在不少的鼎鼎大名的開源庫裏運用很普遍,好比redux、koa等。特別是redux,裏面有不少中間件本質其實就是高階函數。

組件開發

  1. 組件開發的目的其實就是暴露出對外使用的接口。使用者只須要定義好訂閱的事件,至於組件內部如何去實現的過程,對使用者來講,這並非他所關心的。使用者只須要關心接口的入參和出參,是否異步等這些關鍵信息便可。
  2. 如何寫出一個擴展性和穩定性都很好的接口,確實會考驗開發者的設計水平。每每當用戶有新的需求,然而目前的項目卻不能支持,不得不面臨着升級(萬一開發者不能及時或者不升級呢?)。因此,優秀的開發者在定義接口時,會考慮好各類狀況,而且將高度的自定義權利移交給使用者,雖然這樣會給開發帶來複雜度,可是帶給用戶的好處倒是成倍的。

舉個vue的🌰

<div id="app" class='container'>
    <div style='margin: 50px'>{{title}}</div>
    <div style='margin: 20px; font-size: 16px'>{{message}}</div>
    <my-button :tap='tapString' type='我是字符串'></my-button>
    <my-button :tap='tapFunction' type='我是函數'></my-button>
    <my-button :tap='tapPromise' type='我是promise'></my-button>
  </div>
  
複製代碼
<script>
  // 子組件
  Vue.component('my-button', {
    props: ['type'],
    methods: {
      handleClickMe: function () {
        // 普通返回
        var value = '我是一個普通數據類型,好比字符串,數字'
        var middleware = this.$attrs.tap(value)
        if (!middleware) {
          console.log('返回undefined,直接終止')
          return
        }
        if (typeof middleware === 'function') {
          middleware('我是一個函數')
        } else if (typeof middleware === 'object' && typeof middleware.then === 'function') {
          middleware.then(({ payload, callback }) => {
            typeof callback === 'function' && callback(payload + ', world!')
          }).catch(({ payload, callback }) => {
            typeof callback === 'function' && callback(payload + ', 不要緊!')
          })
        }
        // ...其餘類型
      }
    },
    template: '<button v-on:click="handleClickMe">點我, {{type}} </button>'
  })
  // 父組件
  var app = new Vue({
    el: '#app',
    data: {
      title: '父組件',
      message: ''
    },
    methods: {
      tapString: function (res) {
        this.message = res
        // alert(res)
      },
      tapFunction: function (res) {
        var _this = this
        return function (data) {
          _this.message = data
          // alert(data)
        }
      },
      tapPromise: function (res) {
        var _this = this
        return new Promise((resolve, reject) => {
          _this.message = ''
          var number = Math.random()
          setTimeout(() => {
            if (number > 0.5) {
              resolve({
                payload: 'hello',
                callback: function (res) {
                  _this.message = res
                  // alert(res)
                }
              })
            } else {
              reject({
                payload: '發生錯誤了',
                callback: function (res) {
                  _this.message = res
                  // alert(res)
                }
              })
            }
          }, 1000)
        })
      }
    }
  })
</script>
複製代碼
  1. 子組件有個tap屬性的接口,當子組件觸發click事件,會觸發(發佈)父組件訂閱的tap方法。一般咱們寫的組件的屬性方法,相似tapString這樣,經過函數參數的形式,獲取自組件傳遞過來的結果便可。
  2. 若是換成高階組件,能夠返回一個函數,或者Promise甚至其餘類型,這樣自組件第一步執行tap屬性函數,會獲取一個函數執行的返回結果 var middleware = this.$attrs.tap(value),而後經過判斷這個結果的類型,若是是函數或者是promise,再進行相應的處理便可。
if (!middleware) {
  return
}
if (typeof middleware === 'function') {
  middleware('我是一個函數')
} else if (typeof middleware === 'object' && typeof middleware.then === 'function') {
  middleware.then(({ payload, callback }) => {
    typeof callback === 'function' && callback(payload + ', world!')
  }).catch(({ payload, callback }) => {
    typeof callback === 'function' && callback(payload + ', 不要緊!')
  })
}
複製代碼
  1. 經過這種高階函數的一個接口,就能知足用戶在不一樣的使用場景下,都能獲取滿意的結果。好比異步獲取的信息傳遞給組件,最後又從組件那裏獲取傳遞過來的結果信息。
  2. 這個🌰一樣適用於react組件,只不過其中的語法不一樣而已。
相關文章
相關標籤/搜索