今天又写了5个比较常用的自定义指令,感觉Vue3写起来确实比Vue2舒服一些。
- 限制数字输入 v-inputNumber,可以设置小数位数
- 提示信息v-tooltip,使用el-tooltip做的指令
- 拷贝 v-copy,复制文本到剪切板
- 节流 v-throttle
防抖 v-debounce
不知不觉加了这么多自定义指令了,所以把入口文件index.ts改了一下,更加自动化,无须重复写import和app.directive注册指令
import { App } from 'vue'
const modules = import.meta.glob('../directive/**/*.ts', {
eager: true
})
let mapDirective = new Map()
Object.keys(modules).forEach((key) => {
if (modules[key] && modules[key].default) {
const newKey = key.replace(/^\.\/|\.ts|\.js/g, '')
mapDirective.set(newKey, modules[key].default)
}
})
export default (app: App) => {
mapDirective.forEach((value, key) => {
app.directive(key, value)
})
晚上了,dark模式自动变黑,所以截的图都是黑的。
v-inputNumber
Element Plus的el-number很好用,但还是有一些场景会用指令或方法来处理。
此指令限制用户只能输入数字,可以设置小数位数限制
可选指令值
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
decimal | 小数位数 | 数字 | 2 |
在模板中使用
<el-input v-model="inputValue1" v-inputNumber />
<el-input v-model="inputValue2" v-inputNumber="{ decimal: 3 }" />
指令代码
// 限制输入数字
import { DirectiveBinding } from 'vue'
interface ExHTMLElement extends HTMLElement {
inputListener: EventListener
}
export default {
mounted(el: ExHTMLElement, binding: DirectiveBinding) {
const decimal = binding.value?.decimal || 2
const elInput = el.getElementsByTagName('input')[0]
let regDecimal: RegExp
if (decimal > 0) regDecimal = new RegExp(`^\\d*(.?\\d{0,${decimal}})`, 'g')
else regDecimal = new RegExp(`^\\d*`, 'g')
el.inputListener = () => {
let val = elInput.value
elInput.value =
val
.replace(/[^\d^\.]+/g, '')
.replace(/^0+(\d)/, '$1')
.replace(/^\./, '0.')
.match(regDecimal)[0] || ''
}
elInput.addEventListener('input', el.inputListener)
},
unmounted(el: ExHTMLElement) {
el.getElementsByTagName('input')[0].removeEventListener('input', el.inputListener)
}
}
v-tooltip
在元素上使用该指令,元素左边/右边会自动加上问号图标,图标附带tooltip。因为使用el-tooltip来开发的这个指令,所以可选指令可以根据el-tooltip的属性继续扩展。
可选指令值
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
message | 提示文字内容,为空不会显示tooltip图标 | string | - |
position | 提示图标位置 | enum:left\right | left |
effect | tooltip主题 | enum:light\dark | light |
placement | tooptip出现的位置 | enum:top\top-start\top-end\bottom\bottom-start\bottom-end\left\left-start\left-end\right\right-start\right-end | top |
在模板中使用
在模板中使用,比直接使用el-tooltip能减少一点代码量,稍微简洁一些。
<div v-tooptip="{ message: '我是一个说明', effect: 'dark' }">我有一个说明文字</div>
<div v-tooptip="{ message: '我是一个说明', position: 'right' }">我有一个说明文字在右边</div>
指令代码
此指令实现的核心是Vue3的h函数,可以让我们在js中生成vnode并渲染,这样就可以使用Element plus的组件进行渲染。如果光靠html node的createElement、append这些方法无法实现此指令。
虽然走了一些弯路,不过折磨自己写完了也就明白了,而且对h函数更熟悉了,它是让我今天收获最多的。
专门写了一篇文章学习渲染函数 :入门Vue3使用渲染函数、h函数
import { DirectiveBinding, h, render } from 'vue'
import { ElTooltip, ElTag } from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue'
export default {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const message = binding.value.message
const placement = binding.value.placement || 'top'
const effect = binding.value.effect || 'light'
const position = binding.value.position || 'left'
if (binding.value.message) {
const vnode = h(
ElTooltip,
{ content: message, placement, effect },
h(QuestionFilled, { style: { width: '16px' } })
)
const dom = document.createElement('span')
if (position === 'left') el.prepend(dom)
else el.append(dom)
render(vnode, dom)
}
}
}
可以说这个是今天最费劲的一个指令,虽然代码看着很简单。因为用到了h函数,平时用得少,放在指令还是第一次,好几次想放弃,还是坚持写完了。
v-copy 复制文本到剪切板
此指令用于复制文本到剪贴板中
可选指令值
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
position | 相对目标元素的位置,可选'out' | string | - |
模板中使用
<div v-copy>222</div>
<div><el-tag v-copy="{ position: 'out' }">343434</el-tag></div>
需要先安装vue3-clipboard
图标处理这块不太优雅,可以改成用h函数渲染一个elicon,先放着有时间改改。
import { DirectiveBinding } from 'vue'
import { copyText } from 'vue3-clipboard'
import { ElMessage } from 'element-plus'
interface ExHTMLElement extends HTMLElement {
clickListener: EventListener
trigger?: HTMLElement
}
// 复制图标
const svg =
'<svg viewBox="0 0 1024 1024" width="1.25em" height="1.2em"><path fill="currentColor" d="M768 832a128 128 0 0 1-128 128H192A128 128 0 0 1 64 832V384a128 128 0 0 1 128-128v64a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64h64z"></path><path fill="currentColor" d="M384 128a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64V192a64 64 0 0 0-64-64H384zm0-64h448a128 128 0 0 1 128 128v448a128 128 0 0 1-128 128H384a128 128 0 0 1-128-128V192A128 128 0 0 1 384 64z"></path></svg>'
export default {
mounted(el: ExHTMLElement, binding: DirectiveBinding) {
// 动态增加复制图标
el.trigger = document.createElement('span')
el.trigger.style.marginLeft = '4px'
el.trigger.style.cursor = 'pointer'
el.trigger.innerHTML = svg
// 复制图标的位置
if (binding.value?.position === 'out') el.after(el.trigger)
else el.append(el.trigger)
el.clickListener = () => {
const text = el.innerText
copyText(text, undefined, (error: string, event: Event) => {
if (error) {
ElMessage({ type: 'error', message: '未能复制', duration: 2000 })
console.log(error)
} else {
ElMessage({ type: 'success', message: '复制成功', duration: 2000 })
console.log(event)
}
})
}
el.trigger.addEventListener('click', el.clickListener)
},
unmounted(el: ExHTMLElement) {
el.trigger?.removeEventListener('resize', el.clickListener)
}
}
v-throttle
节流策略(throttle),顾名思义,可以减少一段时间内事件的触发频率
此指令应用在按钮上,当用户重复点击按钮,指定时间内只执行一次操作或请求。
模板中使用
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
time | 时间间隔(毫秒) | 数字 | 1000 |
模板中使用
<el-button type="primary" v-throttle @click="testThrottle(100)">
点击我(间隔1秒)!
</el-button>
<el-button type="primary" v-throttle="{ time: 3000 }" click="testThrottle(200)">点击我(间隔3秒)!
</el-button >
// 节流
// 防止按钮多次点击,多次请求
import { DirectiveBinding } from 'vue'
export default {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const time = binding.value?.time || 1000
el.timer = null
el.addEventListener('click', () => {
el.disabled = true
if (el.timer !== null) {
clearTimeout(el.timer)
el.timer = null
el.disabled = true
}
el.timer = setTimeout(() => {
el.disabled = false
}, time)
})
}
}
v-debounce
防抖策略(debounce)是当事件被触发后,延迟n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。
此指令接收一个Function,用于延迟该方法的执行。
可选指令值
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
time | 时间间隔(毫秒) | 数字 | 1000 |
func | 延迟执行的方法 | Function | - |
// 节流
// 防止按钮多次点击,多次请求
import { DirectiveBinding } from 'vue'
export default {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const time = binding.value?.time || 1000
const func = binding.value?.func || null
el.timer = null
el.addEventListener('click', () => {
if (el.timer !== null) {
clearTimeout(el.timer)
el.timer = null
}
el.timer = setTimeout(() => {
func && func()
}, time)
})
}
}
以上5个指令都比较简单,扩展和优化的空间也有不少,可以根据实际需要修改并完善。
在此抛砖引玉,期待大家的想法和好的建议。
项目地址
本项目GIT地址:github.com/lucidity99/…
如果有帮助,给个star ✨ 点个赞👍
1 条评论
过来瞅瞅~~