大写
# 题目:
首先我们看到这里题目的描述,创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值首字母转为大写,v-model 我们都用过,那么它具体是怎么工作的呢?
# 分析:
要实现这个转大写指令的逻辑,我们会用到 vue 源码中 vModelText 这个指令对象,来看一下它内部的实现。
const getModelAssigner = (vnode: VNode): AssignerFn => {
const fn = vnode.props!["onUpdate:modelValue"];
return isArray(fn) ? (value) => invokeArrayFns(fn, value) : fn;
};
function onCompositionStart(e: Event) {
(e.target as any).composing = true;
}
function onCompositionEnd(e: Event) {
const target = e.target as any;
if (target.composing) {
target.composing = false;
trigger(target, "input");
}
}
function trigger(el: HTMLElement, type: string) {
const e = document.createEvent("HTMLEvents");
e.initEvent(type, true, true);
el.dispatchEvent(e);
}
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
> = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode);
const castToNumber = number || el.type === "number";
addEventListener(el, lazy ? "change" : "input", (e) => {
if ((e.target as any).composing) return;
let domValue: string | number = el.value;
if (trim) {
domValue = domValue.trim();
} else if (castToNumber) {
domValue = toNumber(domValue);
}
el._assign(domValue);
});
if (trim) {
addEventListener(el, "change", () => {
el.value = el.value.trim();
});
}
if (!lazy) {
addEventListener(el, "compositionstart", onCompositionStart);
addEventListener(el, "compositionend", onCompositionEnd);
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
addEventListener(el, "change", onCompositionEnd);
}
},
// set value on mounted so it's after min/max for type="range"
mounted(el, { value }) {
el.value = value == null ? "" : value;
},
beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {
el._assign = getModelAssigner(vnode);
// avoid clearing unresolved text. #2302
if ((el as any).composing) return;
if (document.activeElement === el) {
if (trim && el.value.trim() === value) {
return;
}
if ((number || el.type === "number") && toNumber(el.value) === value) {
return;
}
}
const newValue = value == null ? "" : value;
if (el.value !== newValue) {
el.value = newValue;
}
},
};
虽然内容很多,但可以总结为以下几点:
1、当执行 created 钩子时: 拿到需要执行的函数: 通过 getModelAssigner 方法,从 vnode 的 props 上提取 根据 lazy 来给 input 元素注册 change 或者 input 事件 根据 trim 来给 input 元素再次注册一个 change 事件 根据 lazy 来给 input 元素分别注册 compositionstart compositionend change 事件为了修复 Safari < 10.2 的 bug
2、当执行 mounted 钩子时: 为 type="range" 的 input 元素 做特殊处理
3、当执行 beforeUpdate 钩子时: 拿到需要执行的函数 避免清除未解析的文本 给 value 赋值
# 解决:
<script setup>
// 导入 ref 和 vModelText 函数
import { ref, vModelText } from "vue";
// 定义一个响应式变量 value
const value = ref("");
// 使用 vModelText.beforeUpdate 指令,在更新 value 之前对输入值进行操作
vModelText.beforeUpdate = (el, binding) => {
// 如果输入值不为空且包含 capitalize 修饰符,则将输入值的首字母转换为大写
if (el.value && binding.modifiers.capitalize) {
el.value = el.value.charAt(0).toUpperCase() + el.value.slice(1);
}
};
</script>
<template>
<!-- 创建一个文本输入框,使用 v-model.capitalize 指令将 value 变量的值绑定到输入框的值,并在更新 value 之前对输入值进行操作 -->
<input type="text" v-model.capitalize="value" />
</template>
所以这里我们要做对 input 框输入的 value 值进行大小写的转换的话,是不是就可以使用 vModelText 内部的这个 beforeUpdate 钩子来解决,在值更新前,确保拿到这个 value 值和修饰符后,编写对值的首字母转大写的逻辑,从而完成 v-model.capitalize 这个指令。