Vue 3.0双向数据绑定实现原理

news2025/5/17 14:41:31

Vue3 的数据双向绑定是通过响应式系统来实现的。相比于 Vue2,Vue3 在响应式系统上做了很多改进,主要使用了 Proxy 对象来替代原来的 Object.defineProperty。本文将介绍 Vue3 数据双向绑定的主要特点和实现方式。

1. 响应式系统

1.1. Proxy对象

Vue3 使用 JavaScript 的 Proxy 对象来实现响应式数据。Proxy 可以监听对象的所有操作,包括读取、写入、删除属性等,从而实现更加灵活和高效的响应式数据。

1.2. reactive函数

Vue3 提供了一个 reactive 函数来创建响应式对象,通过 reactive 函数包装的对象会变成响应式数据,Vue 会自动跟踪这些数据的变化。

import { reactive } from 'vue';

const state = reactive({
    message: 'Hello Vue3'
});

1.3. ref函数

对于基本数据类型,如字符串、数字等,Vue3 提供了 ref 函数来创建响应式数据,使用 ref 包装的值可以在模板中进行双向绑定。

import { ref } from 'vue';

const count = ref(0);

2. 双向绑定

Vue3 中的双向绑定主要通过 v-model 指令来实现,适用于表单元素,如输入框、复选框等。以下是一个简单的示例:

<template>
    <input v-model="message" />
    <p>{{ message }}</p>
</template>

<script>
import { ref } from 'vue';

export default {
    setup() {
        const message = ref('');
        return {
            message
        }
    }
}
</script>

在 Vue3 中,v-model 的使用更加灵活,可以支持自定义组件的双向绑定:

<template>
    <CustomInput v-model:value="message" />
</template>

<script>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

export default {
    components: {
        CustomInput
    },
    setup() {
        const message = ref('');
        return {
            message
        }
    }
}
</script>

在 CustomInput 组件中:

<template>
    <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"/>
</template>

<script>
export default {
    props: {
        modelValue: String
    }
};
</script>

下面,我们来深入了解 Vue3 如何通过源码实现数据的双向绑定。

3. 源码实现

3.1. Proxy实现响应式

Vue3 使用 Proxy 对象来实现响应式数据。Proxy 允许我们定义基本操作的自定义行为,如读、写、删除、枚举等。

以下是 Vue3 响应式系统的核心代码片段:

function reactive(target) {
    return createReactiveObject(target, mutableHandlers);
}

const mutableHandlers = {
    get(target, key, receiver) {
        // 依赖收集
        track(target, key);
        const res = Reflect.get(target, key, receiver);
        // 深度响应式处理
        if (isObject(res)) {
            return reactive(res);
        }
        return res;
    },

    set(target, key, value, receiver) {
        const oldValue = target[key];
        const result = Reflect.set(target, key, value, receiver);
        // 触发更新
        if (oldValue != value) {
            trigger(target, key);
        }
        return result;
    },

    // 其他处理函数 (deleteProperty, has, ownKeys 等)
};

function createReactiveObject(target, handlers) {
    if (!isObject(target)) {
        return target;
    }
    const proxy = new Proxy(target, handlers);
    return proxy;
}

3.2. 依赖心集与触发更新

在响应式系统中,依赖收集和触发更新是两个核心概念。Vue3 使用 track和 trigger 函数来实现这两个功能。

const targetMap = new WeakMap();

function track(target, key) {
    const effect = activeEffect;
    if (effect) {
        let depsMap = targetMap.get(target);
        if (!depsMap) {
            targetMap.set(target, (depsMap = new Map()));
        }
        let dep = depsMap.get(key);
        if (!dep) {
            depsMap.set(key, (dep = new Set()));
        }
        if (!dep.has(effect)) {
            dep.add(effect);
            effect.deps.push(dep);
        }
    }
}

function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    const effects = new Set();
    const add = effectsToAdd => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => effects.add(effect));
        }
    };

    add(depsMap.get(key));
    effects.forEach(effect => effect());
}

3.3. ref实现

对于基本数据类型,Vue3 提供了 ref 函数来创建响应式数据。ref 使用一个对象来包装值,并通过 getter和 setter 来实现响应式。

function ref(value) {
    return createRef(value);
}

function createRef(rawValue) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    const r = {
        __v_isRef: true,
        get value() {
            track(r, 'value');
            return rawValue;
        },
        set value(newVal) {
            if (rawValue !== newVal) {
                rawValue = newVal;
                trigger(r, 'value');
            }
        }
    };
    return r;
}

function isRef(r) {
    return r ? r.__v_isRef === true : false;
}

3.4. v-model实现

Vue3 中的 v-model 实现依赖于响应式系统。

3.4.1. 编译时实现

// packages/compiler-core/src/transforms/vModel.ts
export const transformModel: NodeTransform = (node, context) => {
  if (node.type === NodeTypes.ELEMENT) {
    // 对每个元素节点执行此方法
    return () => {
      // 只处理有 v-model 指令的节点
      const node = context.currentNodel
      if (node.tagType === ElementTypes.ELEMENT) {
        const dir = findDir(node, 'model')
        if (dir && dir.exp) {
          // 根据节点类型调用不同的处理函数
          const { tag } = node
          if (tag === 'input') {
            processInput(node, dir, context)
          } else if (tag === 'textarea') {
            processTextArea(node, dir, context)
          } else if (tag === 'select') {
            processSelect(node, dir, context)
          } else if (!context.inSSR) {
            // 组件上的 v-model
            processComponent(node, dir, context)
          }
        }
      }
    }
  }
}


// 处理组件上的v-model
function processComponent(
  node: ElementNode,
  dir: DirectiveNode,
  context: TransformContext
) {
  // 获取 v-model 的参数,支持 v-model:arg 形式
  const { arg, exp } = dir
  
  // 默认参数是 'modelValue'
  const prop = arg ? arg : createSimpleExpression('modelValue', true)
  
  // 默认事件是 'update:modelValue'
  const event = arg
    ? createSimpleExpression(`update:${arg.content}`, true)
    : createSimpleExpression('update:modelValue', true)
  
  // 添加 prop 和 event 到 props 中
  const props = [
    createObjectProperty(prop, dir.exp!),
    createObjectProperty(event, createCompoundExpression([`$event => ((`, exp, `) = $event)`]))
  ]
  
  // 将 v-model 转换为组件的 props 和事件
  node.props.push(
    createObjectProperty(
      createSimpleExpression(`onUpdate:modelValue`, true),
      createCompoundExpression([`$event => (${dir.exp!.content} = $event)`])
    )
  )
}

3.4.2. 运行时实现

// packages/runtime-core/src/helpers/vModel.ts
export function vModelText(el: any, binding: any, vnode: VNode) {
  // 处理文本输入框的 v-model
  const { value, modifiers } = binding
  el.value = value == null ? '' : value
  
  // 添加事件监听
  el._assign = getModelAssigner(vnode)
  const lazy = modifiers ? modifiers.lazy : false
  const event = lazy ? 'change' : 'input'
  
  el.addEventListener(event, e => {
    // 触发更新
    el._assign(el.value)
  })
}

export function vModelCheckbox(el: any, binding: any, vnode: VNode) {
  // 处理复选框的 v-model
  const { value, oldValue } = binding
  el._assign = getModelAssigner(vnode)
  
  // 处理数组类型的值(多选)
  if (isArray(value)) {
    const isChecked = el._modelValue? looseIndexOf(value, el._modelValue) > -1: false
    if (el.checked !== isChecked) {
      el.checked = isChecked
    }
  } else {
    // 处理布尔值
    if (value !== oldValue) {
      el.checked = looseEqual(value, el._trueValue)
    }
  }
}

// 辅肋函数
function getModelAssigner(vnode: VNode): (value: any) => void {
  // 获取模型赋值函数
  const fn = vnode.props!['onUpdate:modelValue']
  return isArray(fn) ? (value: any) => invokeArrayFns(fn, value) : fn
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Please install it with pip install onnxruntime

无论怎么安装都是 Please install it with pip install onnxruntime 我python 版本是3.11 &#xff0c;我换成3.10 解决了

低损耗高效能100G O Band DWDM 10km光模块 | 支持密集波分复用

目录 前言 一、产品概述 100G QSFP28 O Band DWDM 10km光模块核心特点包括&#xff1a; 二、为何选择O Band DWDM方案&#xff1f; 1.低色散损耗&#xff0c;传输更稳定 2.兼容性强 三、典型应用场景 1.数据中心互联&#xff08;DCI&#xff09; 2.企业园区/智慧城市组网 3.电信…

第二十六天打卡

全局变量 global_var 全局变量是定义在函数、类或者代码块外部的变量&#xff0c;它在整个程序文件内都能被访问。在代码里&#xff0c; global_var 就是一个全局变量&#xff0c;下面是相关代码片段&#xff1a; print("\n--- 变量作用域示例 ---") global_var …

阿里云ECS部署Dify

一&#xff1a;在ECS上面安装Docker 关防火墙 sudo systemctl stop firewalld 检查防火墙状态 systemctl status firewalld sudo yum install -y yum-utils device-mapper-persistent-data lvm2 设置阿里镜像源&#xff0c;安装并启动docker [base] nameCentOS-$releas…

日志与策略模式

什么是设计模式 IT⾏业 ,为了让 菜鸡们不太拖⼤佬的后腿, 于是⼤佬们针对⼀些经典的常⻅的场景, 给定了⼀些对应的解决⽅案, 这个就是 设计模式 日志认识 计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件&#xff0c;主要作⽤是监控运⾏状态、记录异常信 息&#xff…

Jenkins 最佳实践

1. 在Jenkins中避免调度过载 过载Jenkins以同时运行多个作业可能导致资源竞争、构建速度变慢和系统性能问题。分配作业启动时间可以防止瓶颈&#xff0c;并确保更顺畅的执行。如何实现&#xff1f; 在Cron表达式中使用H&#xff1a;引入抖动&#xff08;jitter&#xff09;&a…

天能股份SAP系统整合实战:如何用8个月实现零业务中断的集团化管理升级

目录 天能股份SAP系统整合案例&#xff1a;技术驱动集团化管理的破局之路 一、企业背景&#xff1a;新能源巨头的数字化挑战 二、项目难点&#xff1a;制造业的特殊攻坚战 1. 生产连续性刚性需求 2. 数据整合三重障碍 3. 资源限制下的技术突围 三、解决方案&#xff1a;S…

uniapp-商城-59-后台 新增商品(属性的选中,进行过滤展示,filter,some,every和map)

前面讲了属性的添加&#xff0c;添加完成后&#xff0c;数据库中已经存在数据了&#xff0c;这时再继续商品的添加时&#xff0c;就可以进行属性的选择了。 在商品添加过程中&#xff0c;属性选择是一个关键步骤。首先&#xff0c;界面需要展示嵌套的属性数据&#xff0c;用户通…

B2C 商城转型指南:传统企业如何用 ZKmall模板商城实现电商化

在数字化浪潮席卷全球的当下&#xff0c;传统企业向电商转型已不再是选择题&#xff0c;而是关乎生存与发展的必答题。然而&#xff0c;缺乏技术积累、开发成本高、运营经验不足等问题&#xff0c;成为传统企业转型路上的 “拦路虎”。ZKmall模板商城以其低门槛、高灵活、强适配…

生成树协议 - STP

目录 BPDU STP选举机制 STP端口状态 STP计时器 STP拓扑变更机制 生成树协议&#xff08;Spanning Tree Protocol&#xff09;&#xff0c;简写为STP。 STP是二层网络中用于消除环路的协议&#xff0c;通过阻塞冗余链路&#xff0c;使可用链路在拓扑上呈现出无环的树结构&…

计算机指令分类和具体的表示的方式

1.关于计算机的指令系统 下面的这个就是我们的一个简单的计算机里面涉及到的指令&#xff1a; m就是我们的存储器里面的地址&#xff0c;可以理解为memory这个意思&#xff0c;r可以理解为rom这样的单词的首字母&#xff0c;帮助我们去进行这个相关的指令的记忆&#xff0c;不…

mvc-service引入

什么是业务层 1&#xff09;Model1&#xff08;JSP&#xff09;和Model2&#xff08;模糊的mvc&#xff09;: MVC&#xff1a;Model(模型)&#xff0c;View(视图)&#xff0c;Controller&#xff08;控制器&#xff09; 视图层&#xff1a;用于数据展示以及用户交互的界…

基于微信小程序的城市特色旅游推荐应用的设计与实现

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

【暗光图像增强】【基于CNN的方法】2020-AAAI-EEMEFN

EEMEFN&#xff1a;Low-Light Image Enhancement via Edge-Enhanced Multi-Exposure Fusion Network EEMEFN&#xff1a;基于边缘增强多重曝光融合网络的低光照图像增强 AAAI 2020 论文链接 0.论文摘要 本研究专注于极低光照条件下的图像增强技术&#xff0c;旨在提升图像亮度…

【Linux】ssh命令 – 安全的远程连接服务

原创&#xff1a;厦门微思网络 SSH命令的概念 ssh命令的功能是安全地远程连接服务器主机系统&#xff0c;作为OpenSSH套件中的客户端连接工具&#xff0c;ssh命令可以让我们轻松地基于SSH加密协议进行远程主机访问&#xff0c;从而实现对远程服务器的管理工‍作。 语法 ssh 参…

AT9850B—单北斗导航定位芯片

AT9850B是一款高性能低功耗双频单北斗卫星导航接收机SOC单芯片。芯片集成射频前端和数字基带、多模式卫星信号处理引擎、电源管理功能&#xff0c;集成度高&#xff0c;外围应用电路简洁。 支持中国北斗B1I/B1C单频定位或B1I/B1C/B2a双频定位&#xff0c;支持北斗二号和三号&a…

工业4G路由器IR5000公交站台物联网应用解决方案

随着城市化进程的加速&#xff0c;公共交通是智慧城市的重要枢纽。城市公共交通由无数的公交站台作作为节点组合而成&#xff0c;其智能化升级成为提升城市出行效率与服务质量的关键。传统公交站台信息发布滞后、缺乏实时性&#xff0c;难以满足乘客对公交信息快速获取的需求&a…

idea中Lombok失效的解决方案

Lombok 是一个 Java 库&#xff0c;旨在通过注解简化 Java 代码的编写&#xff0c;减少样板代码&#xff0c;提高开发效率。它通过自动生成常见的代码&#xff08;如 getter、setter、构造函数等&#xff09;来减少开发者的手动编码工作。 一般Lombok失效有四步排查方案&#…

黑马k8s(九)

1.Pod-生命周期概述 2.Pod生命周期-创建和终止 3.Pod生命周期-初始化容器

【超分辨率专题】一种考量视频编码比特率优化能力的超分辨率基准

这是一个Benchmark&#xff0c;超分辨率视频编码&#xff08;2024&#xff09; 专题介绍一、研究背景二、相关工作2.1 SR的发展2.2 SR benchmark的发展 三、Benchmark细节3.1 数据集制作3.2 模型选择3.3 编解码器和压缩标准选择3.4 Benchmark pipeline3.5 质量评估和主观评价研…