当前位置: 首页 > news >正文

vue3:v-model的原理示例

模拟 Vue 3 的完整 v-model 功能,我们需要实现以下特性:**

  1. 表单元素的双向绑定:支持 等表单元素,包括修饰符(如 .lazy.number.trim)。
  2. 自定义组件的双向绑定:支持 v-model 在组件上的使用,基于 modelValueupdate:modelValue,并支持多重 v-model(如 v-model:name)。
  3. 自定义修饰符:支持组件上的自定义修饰符(如 v-model.capitalize)。
  4. 响应式系统:实现一个简化的响应式系统,模拟 Vue 3 的 refProxy 行为,确保数据变化触发视图更新。

原生 JavaScript 实现,模拟 Vue 3 的 v-model 功能,包括表单元素和组件支持。我们将逐步构建代码,并提供使用示例。**


实现步骤

1. 响应式系统(模拟 ref)

我们需要一个简单的响应式系统,类似于 Vue 3 的 ref,用于创建响应式数据并在数据变化时通知依赖。

javascript

// 定义 Dep 对象,用于依赖收集
let Dep = {target: null // 当前依赖的回调函数
};// 模拟 Vue 3 的 ref
function ref(initialValue) {const deps = new Set();let _value = initialValue;return {get value() {// 依赖收集if (typeof Dep.target === 'function') {deps.add(Dep.target);}return _value;},set value(newValue) {if (_value !== newValue) {_value = newValue;// 通知依赖deps.forEach((dep) => dep());}}};
}// 依赖收集的全局变量
Dep.target = null;// 模拟 watchEffect,用于依赖收集
function watchEffect(callback) {Dep.target = callback;callback();Dep.target = null;
}
  • ref 创建响应式数据,getter 收集依赖,setter 通知依赖更新。
  • watchEffect 模拟 Vue 的依赖收集机制,执行回调并收集依赖。

2. 表单元素的 v-model

表单元素的 v-model:value@input(或 @change)的语法糖。我们需要:

  • 绑定响应式数据到元素的 value(或 checked)。
  • 监听输入事件,更新响应式数据。
  • 支持修饰符(如 .lazy.number**、.trim)。**

javascript

// 处理修饰符逻辑
function applyModifiers(value, modifiers) {let result = value;if (modifiers.number) {result = isNaN(Number(result)) ? result : Number(result);}if (modifiers.trim && typeof result === 'string') {result = result.trim();}return result;
}// 表单元素的 v-model
function vModelForm(element, reactiveRef, modifiers = {}) {const tag = element.tagName.toLowerCase();const type = element.type;// 确定事件类型const eventName = modifiers.lazy? 'change': tag === 'select' || type === 'checkbox' || type === 'radio'? 'change': 'input';// 更新视图const updateView = () => {if (type === 'checkbox') {element.checked = reactiveRef.value;} else if (type === 'radio') {element.checked = reactiveRef.value === element.value;} else {element.value = reactiveRef.value;}};// 初始绑定和依赖收集Dep.target = updateView;updateView();Dep.target = null;// 监听输入事件element.addEventListener(eventName, (event) => {let newValue;if (type === 'checkbox') {newValue = event.target.checked;} else if (type === 'radio') {if (event.target.checked) {newValue = event.target.value;} else {return; // 未选中时不更新}} else {newValue = event.target.value;}// 应用修饰符newValue = applyModifiers(newValue, modifiers);reactiveRef.value = newValue;});// 数据变化时更新视图reactiveRef.deps = reactiveRef.deps || new Set();reactiveRef.deps.add(updateView);
}
  • 修饰符处理applyModifiers 处理 .number.trim 修饰符。
  • 事件选择:根据元素类型(checkboxradioselect)或修饰符(.lazy)选择 inputchange 事件。
  • 视图更新:根据元素类型更新 valuechecked
  • 事件监听:根据元素类型获取用户输入的值,并应用修饰符后更新响应式数据。

3. 自定义组件的 v-model

组件的 v-model 基于 modelValueupdate:modelValue,支持多重 v-model 和自定义修饰符。我们模拟组件为一个简单的 DOM 结构,包含子组件逻辑。

javascript

// 模拟组件的 v-model
function vModelComponent(element, reactiveRef, propName = 'modelValue', modifiers = {}) {// 模拟组件的 props 和 emitconst props = { [propName]: reactiveRef.value };const emit = (eventName, value) => {if (eventName === `update:${propName}`) {// 应用修饰符let newValue = value;if (modifiers.capitalize && typeof newValue === 'string') {newValue = newValue.charAt(0).toUpperCase() + newValue.slice(1);}reactiveRef.value = newValue;}};// 更新视图const updateView = () => {element.value = reactiveRef.value; // 模拟组件内部 input 的 value};// 初始绑定Dep.target = updateView;updateView();Dep.target = null;// 模拟组件内部的 input 事件element.addEventListener('input', (event) => {emit(`update:${propName}`, event.target.value);});// 数据变化时更新视图reactiveRef.deps = reactiveRef.deps || new Set();reactiveRef.deps.add(updateView);
}
  • props 和 emit:模拟组件的 props(如 modelValue)和 $emit(如 update:modelValue)。
  • 多重 v-model:通过 propName 支持自定义属性(如 nameage)。
  • 修饰符:支持自定义修饰符(如 .capitalize),在 emit 时处理。
  • 视图更新:模拟组件内部的 ,绑定 value 并监听 input 事件。

4. 统一的 v-model 接口

创建一个统一的 vModel 函数,根据上下文(表单元素或组件)调用不同的实现。

javascript

function vModel(element, reactiveRef, options = {}) {const { modifiers = {}, propName } = options;const isComponent = propName || element.tagName.toLowerCase() === 'custom-component';if (isComponent) {vModelComponent(element, reactiveRef, propName || 'modelValue', modifiers);} else {vModelForm(element, reactiveRef, modifiers);}
}
  • 选项modifiers 指定修饰符,propName 指定组件的绑定属性。
  • 上下文判断:根据 propName 或元素标签判断是表单元素还是组件。

5. 完整代码与使用示例

javascript

// 定义 Dep 对象,用于依赖收集
const Dep = {target: null, // 当前依赖的回调函数
};// 模拟 Vue 3 的 ref
function ref(initialValue) {const deps = new Set();let _value = initialValue;return {get value() {// 依赖收集if (typeof Dep.target === 'function') {deps.add(Dep.target);}return _value;},set value(newValue) {if (_value !== newValue) {_value = newValue;// 通知依赖deps.forEach((dep) => dep());}},};
}// 模拟 watchEffect,用于依赖收集
function watchEffect(callback) {Dep.target = callback;callback();Dep.target = null;
}// 处理修饰符逻辑
function applyModifiers(value, modifiers) {let result = value;if (modifiers.number) {result = isNaN(Number(result)) ? result : Number(result);}if (modifiers.trim && typeof result === 'string') {result = result.trim();}return result;
}// 表单元素的 v-model
function vModelForm(element, reactiveRef, modifiers = {}) {const tag = element.tagName.toLowerCase();const type = element.type;const eventName = modifiers.lazy? 'change': tag === 'select' || type === 'checkbox' || type === 'radio'? 'change': 'input';const updateView = () => {if (type === 'checkbox') {element.checked = reactiveRef.value;} else if (type === 'radio') {element.checked = reactiveRef.value === element.value;} else {element.value = reactiveRef.value;}};Dep.target = updateView;updateView();Dep.target = null;element.addEventListener(eventName, (event) => {let newValue;if (type === 'checkbox') {newValue = event.target.checked;} else if (type === 'radio') {if (event.target.checked) {newValue = event.target.value;} else {return;}} else {newValue = event.target.value;}newValue = applyModifiers(newValue, modifiers);reactiveRef.value = newValue;});reactiveRef.deps = reactiveRef.deps || new Set();reactiveRef.deps.add(updateView);
}// 组件的 v-model
function vModelComponent(element,reactiveRef,propName = 'modelValue',modifiers = {},
) {const props = { [propName]: reactiveRef.value };const emit = (eventName, value) => {if (eventName === `update:${propName}`) {let newValue = value;if (modifiers.capitalize && typeof newValue === 'string') {newValue = newValue.charAt(0).toUpperCase() + newValue.slice(1);}reactiveRef.value = newValue;}};const updateView = () => {element.value = reactiveRef.value;};Dep.target = updateView;updateView();Dep.target = null;element.addEventListener('input', (event) => {emit(`update:${propName}`, event.target.value);});reactiveRef.deps = reactiveRef.deps || new Set();reactiveRef.deps.add(updateView);
}// 统一 v-model 接口
function vModel(element, reactiveRef, options = {}) {const { modifiers = {}, propName } = options;const isComponent =propName || element.tagName.toLowerCase() === 'custom-component';if (isComponent) {vModelComponent(element, reactiveRef, propName || 'modelValue', modifiers);} else {vModelForm(element, reactiveRef, modifiers);}
}// 使用示例
// 创建 DOM 元素
const inputText = document.createElement('input');
inputText.placeholder = '输入文本';
const inputNumber = document.createElement('input');
inputNumber.type = 'number';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
const select = document.createElement('select');
select.innerHTML = `<option value="option1">选项 1</option><option value="option2">选项 2</option>`;
const customComponent = document.createElement('input'); // 模拟组件
customComponent.placeholder = '自定义组件输入';// 添加到页面
document.body.appendChild(inputText);
document.body.appendChild(document.createElement('br'));
document.body.appendChild(inputNumber);
document.body.appendChild(document.createElement('br'));
document.body.appendChild(checkbox);
document.body.appendChild(document.createElement('br'));
document.body.appendChild(select);
document.body.appendChild(document.createElement('br'));
document.body.appendChild(customComponent);// 创建响应式数据
const message = ref('Hello');
const number = ref(42);
const checked = ref(false);
const selected = ref('option1');
const componentName = ref('alice');
const componentAge = ref(25);// 绑定 v-model
vModel(inputText, message, { modifiers: { trim: true } });
vModel(inputNumber, number, { modifiers: { number: true } });
vModel(checkbox, checked);
vModel(select, selected);
vModel(customComponent, componentName, {propName: 'name',modifiers: { capitalize: true },
});// 模拟多重 v-model
const componentAgeInput = document.createElement('input');
componentAgeInput.type = 'number';
document.body.appendChild(document.createElement('br'));
document.body.appendChild(componentAgeInput);
vModel(componentAgeInput, componentAge, {propName: 'age',modifiers: { number: true },
});// 打印数据变化
watchEffect(() => {console.log('message:', message.value);console.log('number:', number.value);console.log('checked:', checked.value);console.log('selected:', selected.value);console.log('componentName:', componentName.value);console.log('componentAge:', componentAge.value);
});// 程序化更新数据
setTimeout(() => {message.value = 'Updated Text';componentName.value = 'bob';
}, 2000);

执行流程图

初始化:定义 Dep, ref, vModel 等创建 DOM 元素 -> 添加到页面创建 ref 数据 (message, number, ...)vModel 绑定:-> vModelForm 或 vModelComponent-> 确定事件 (input/change)-> 定义 updateView-> 初始更新 DOM (触发 getter, 收集 updateView)-> 绑定事件监听-> 添加 updateView 到 depswatchEffect:-> 打印初始值 (触发 getter, 收集 watchEffect)设置定时器用户交互 (例如输入 " Hello Vue "):input 事件 -> 获取值 " Hello Vue "applyModifiers (.trim) -> "Hello Vue"message.value = "Hello Vue" -> setter-> 更新 _value-> 通知 deps:-> updateView: inputText.value = "Hello Vue"-> watchEffect: 打印所有数据程序化更新 (2秒后):message.value = 'Updated Text' -> setter-> 更新 _value-> 通知 deps:-> updateView: inputText.value = 'Updated Text'-> watchEffect: 打印数据componentName.value = 'bob' -> setter-> 更新 _value-> 通知 deps:-> updateView: customComponent.value = 'bob'-> watchEffect: 打印数据

相关文章:

  • ISO-C99标准 最小限定值
  • 驱动开发硬核特训 │ Regulator 子系统全解
  • IDEA2022.3开启热部署
  • 【React Native】精通 react native
  • 假云阴影模拟
  • 数字孪生三维建模+虚拟仿真,构建可预测的未来工厂
  • QT采用mqtt进行通信(17.1)
  • 小波变换和图像的融合
  • 征程 6 逆向自证 hbm 与 bc 一致性
  • Spring系列五:手动实现Spring底层机制 第一部分
  • 在 Conda 中,包的安装路径在电脑的哪里
  • SwiftUI 10.Toggle介绍和使用
  • 无人设备遥控器之实时数据保护技术篇
  • 60、微服务保姆教程(三)Sentinel---高可用流量管理框架/服务容错组件
  • 新时代下的存储过程开发实践与优化
  • 模板方法模式(Template Method Pattern)
  • 计算机考研精炼 计网
  • Python爬虫技术全解析:从入门到实战的终极指南大纲(深度解读与扩展)
  • 02_使用 AES 算法实现文件加密上传至阿里云、解密下载
  • AGV、AMR机器人控制器x86/RK3588/NV各有什么优劣势?
  • 上海112位全国劳动模范和先进工作者接受表彰,樊振东榜上有名
  • 央行副行长:增强外汇市场韧性,坚决对市场顺周期行为进行纠偏
  • 商务部:将积极会同相关部门加快推进离境退税政策落实落地
  • 专访|伊朗学者:美伊核谈不只是改革派立场,但伊朗不信任美国
  • 美国政府将暂时恢复部分受影响留学生的合法身份,并将制订新标准
  • 人民日报:广东全力推动外贸稳量提质