這幾天學習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)
}
}
})
}
- 引數
- props: 元件的屬性,便於屬性值變更的時候可以獲得最新值。
- context 元件的上下文,方便向父元件提交。
- name:v-model的名稱,預設是 modelValue。
- 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又不支援延遲了。記得之前好用的。。。
優點:
- 自我感覺還是比較優雅的。