vue3自定义数字输入指令
日常的开发过程中可能需要限制用户输入的内容,比如只能输入数字和小数点,又或者是数字加-等等需求,不用废话直接上代码。
import { Directive, ref, DirectiveBinding, Ref, unref, App, nextTick } from 'vue';
import { ElMessage } from 'element-plus'
import { debounce } from 'lodash';
export type Obj = {
maxLength?: number
reg?: RegExp
}
let obj: Ref<Obj> = ref({})
const inputValue = ref()
let reg: RegExp | any = /[^\d]/g
// 警告提示信息
export function warnMsg(msgInfo: string, arg?: any) {
ElMessage({
type: 'warning',
showClose: true,
dangerouslyUseHTMLString: true,
message: msgInfo,
...arg
})
}
export function parseJson(jsonStr: string) {
return JSON.parse(jsonStr, (k, v) => {
try {
// 将正则字符串转成正则对象
if (eval(v) instanceof RegExp) {
return eval(v);
}
} catch (e) {
// nothing
}
return v;
});
}
/**
* json对象转json字符串
* @param { Object } json json对象
*/
export function stringifyJson(json: { [key: string]: any }) {
return JSON.stringify(json, (k, v) => {
// 将正则对象转换为正则字符串
if (v instanceof RegExp) {
return v.toString();
}
return v;
});
}
function tip() {
if (reg.test(inputValue.value)) {
warnMsg('请输入数字!')
}
}
// 派发自定义事件
const trigger = (el: HTMLElement, type: any) => {
const e = document.createEvent('HTMLEvents');
e.initEvent(type, true, true);
el.dispatchEvent(e);
}
export const onlyNumber: Directive = {
mounted(el: any, binding: DirectiveBinding, vnode: any) {
const input = el?.children[0];
if (binding.arg) {
try {
obj.value = parseJson(binding.arg)
if (unref(obj).maxLength) {
input.maxLength = unref(obj)?.maxLength
}
if (unref(obj).reg) {
reg = unref(obj)?.reg
}
} catch (error) {
}
}
if (binding.value) {
reg = binding.value;
}
input.oninput = function (e: any) {
inputValue.value = this.value;
this.value = this.value.replace(reg, '');
if (unref(obj).maxLength) {
if (this.value.length === unref(obj).maxLength) {
warnMsg(`最大输入${unref(obj).maxLength}个字符`)
}
}
// 问题代码 ---> input 改为 update:modelValue
if (vnode.dirs.length) {
vnode.dirs[0].instance.$emit('input', this.value)
}
};
input.addEventListener('input', debounce(tip, 500))
},
beforeUnmount(el: any, binding: any, vnode: any) {
const input = el;
input.removeEventListener('input', tip)
}
}
export default {
install(app: App) {
app.directive('onlyNumber', onlyNumber)
}
}
正如上面代码所示原理很简单通过element-plus的输入框拿到dom,监听input事件,拿到输入的值之后,通过正则进行替换。所有的事情都已经准备完全了,我以为万事俱备了,正当我使用这个指令进行二次封装number输入框的时候,问题就出现了,在vue3中不能通过$emit来触发双向数据绑定。下面是我封装的number组件。
<template>
<el-input v-bind="$attrs" v-onlyNumber:[arg] v-model="val">
<template v-for="(index, name) in slots" :key="index + 'g'" #[name]>
<slot :name="name"></slot>
</template>
</el-input>
</template>
<script setup lang="ts" name="OnlyNumberInput">
import { ref, computed, useSlots, PropType } from 'vue'
export type Arg = {
maxLength?: number
reg?: RegExp
}
const props = defineProps({
modelValue: {
type: String,
default: ''
},
argOptions: {
type: Object as PropType<Arg>,
default: () => {
// maxLength: 5,
// reg: /[^\d\,]/g
}
}
})
const emit = defineEmits(['update:modelValue'])
const arg = stringifyJson(props.argOptions)
const slots = useSlots()
const val = computed({
get() {
return props.modelValue
},
set(v) {
emit('update:modelValue', v)
}
})
/**
* json对象转json字符串
* @param { Object } json json对象
*/
function stringifyJson(json: { [key: string]: any }) {
return JSON.stringify(json, (k, v) => {
// 将正则对象转换为正则字符串
if (v instanceof RegExp) {
return v.toString();
}
return v;
});
}
</script>
讲了这么多废话,到底会出现什么问题呢?
当我们正常输入值的时候如果不是正则匹配的数据就会被替换为空,当输入最后一个值的时候并不会替换为空虽然输入内没有显示,但值已经是为非正则匹配的值了,这不是我们想要的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
如何解决这个问题呢
- 正常的解决方法就是实现element-plus中输入框的数据双向绑定,我想到通过自定义指令中的vnode拿到这个实例的 e m i t 进行触发 i n p u t 事件,在 v u e 2 中这是可行的,在 v u e 3 中并不能通过 ‘ emit进行触发input事件,在vue2中这是可行的,在vue3中并不能通过` emit进行触发input事件,在vue2中这是可行的,在vue3中并不能通过‘emit(‘input’,this.value)
来触发 可以通过***
$emits(“update:modelValue”,this.value)`*** 来触发[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
- 如果不了解这个
update:modelValue
的朋友[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
vue2 vue3 <someComponets :name.sync="name"/>
<someComponets v-model:name="name"/>
emits(“update:name”,value) emits(“udpate:name”,value) 默认为 :value=‘’ @input=“” 默认为 :modelValue=“” @input=“” 组件只能允许一个v-model 可以允许多个v-model绑定 - 暂时就只能想到这么多了。
总结
世上无难事,只要肯放弃就一定能解决问题。