用 customRef 做一個防抖函式,支援 element 等UI庫。

金色海洋(jyk)發表於2021-04-03

這幾天學習Vue的官網,看到 customRef 提供了一個例子,研究半天發現這是一個防抖函式,覺得挺好,於是把這個例子擴充套件了一下,可以用於表單子控制元件和查詢子控制元件。

需求

  • v-model
    基於 element-plus 封裝表單控制元件,同時也要封裝一下表單子控制元件,還有查詢控制元件。
    由於 el-input 這類的元件,把 value 封裝成了 v-model,所以無法把元件的屬性直接設定給內部的 el-input。
    必須在內部設定一個變數,然後做“屬性” <==> “變數” 的轉換。
    這樣就比較麻煩,需要一個既優雅又實用的方式來解決。

  • 查詢的防抖
    查詢的時候,理想情況是使用者輸入一個完整的查詢條件,然後自動去後端申請查詢,但是vue預設的響應形式是,輸入一個字元就會響應,如果立即去後端查詢的話,會造成浪費的情況,另外使用者體驗也不好。
    如果用change事件,那麼使用者輸入完畢,還得在其他的地方點一下,比較麻煩。
    所以需要一個簡單的方式,比如防抖功能來優化使用者體驗。

設計

用 customRef (自定義的ref)設計get 和 set。

  • get:獲取元件屬性,返回給內部元件,比如 el-input。
  • set:用smit提交給父元件。
  • settimeout:實現防抖。

實施驗證

想法挺好的,演示為0的時候也是好用的,但是把延遲設為200的時候確出現問題,首先是 el-input 的字元顯示也一起延遲了,另外只會顯示最後一個字元,中間的字元都被吃掉了。

這是怎麼回事?用html5的 input 試驗的時候是沒有問題的呀。

辦法重臂困難多,幾經修改之後終於好用了。

/**
 * 自定義的ref,實現屬性和內部變數的資料轉換
 * @param { reactive } props 元件的屬性
 * @param { object } context 元件的上下文
 * @param { number } delay 延遲重新整理的時間,單位:毫秒,預設:0
 * @param { string } name 要對應的屬性名稱,預設:modelValue
 * @returns 自定義的ref
 */
export const debounceRef = (props, context, delay = 0, name = 'modelValue') => {
  let value = props[name]
  // 計時器
  let timeout
  // 是否輸入狀態。輸入時取 value;輸入完畢取 modelValue 屬性
  let isInput = false
  return customRef((track, trigger) => {
    return {
      get () {
        track()
        if (isInput) {
          return value
        } else {
          return props[name]
        }
      },
      set (newValue) {
        isInput = true
        value = newValue // 繫結值
        trigger() // 元件內部重新整理模板
        clearTimeout(timeout) // 清掉上一次的計時
        timeout = setTimeout(() => {
          // 修改 modelValue 屬性
          context.emit(`update:${name}`, newValue) // 提交給父元件
          // 用於區分是哪個元件觸發的事件。
          context.emit('my-change', newValue, props.controlId, props.colName)
          isInput = false
        }, delay)
      }
    }
  })
}
  • 引數
  1. props: 元件的屬性,便於屬性值變更的時候可以獲得最新值。
  2. context 元件的上下文,方便向父元件提交。
  3. name:v-model的名稱,預設是 modelValue。
  4. delay:延遲時間,預設是0。
  • value:內部變數,用於初始值和使用者輸入的時候的繫結。

  • let timeout:定時器,便於清掉之前的定時。

  • let isInput = false
    使用者的輸入狀態,如果使用者處於敲鍵盤的狀態,那麼獲取內部的 value 繫結到 el-inupt;
    如果使用者沒有敲鍵盤,那麼獲取父元件的屬性值,繫結到 el-inupt。

為啥要這麼設定呢?沒辦法,如果直接獲取元件的屬性值的話,那麼會出現延遲的情況,如果獲取內部 value 的話,父元件的屬性變化的時候,內部 el-input 不會有變化,所以只好這麼折騰一下。

後面的就是常規操作了,get 裡面根據狀態獲取屬性和 value,set 裡面向父元件提交。

使用

setup (props, context) {
  const value = debounceRef(props, context)
  return {
    value
  }
}
 <el-input v-model="value"></el-input>

基本上和普通的 ref 很像,只是需要設定元件的屬性和上下文。

  • 如果名稱不是預設的 modelValue 的話,需要傳遞名稱;
  • 如果需要延遲響應的話,需要設定延遲時間,預設是不延遲的。

缺點:

  • 靈活性欠佳,只是針對一個特定需求封裝的,沒有考慮更多的情況。其他情況在寫個函式好了,函式要符合原子性,不要承擔太多的職責。

  • 還是要傳遞屬性和上下文,這個也沒啥辦法省略。

  • CheckBox又不支援延遲了。記得之前好用的。。。

優點:

  • 自我感覺還是比較優雅的。

相關文章