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

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

1. 响应式系统

1.1. Proxy对象

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

1.2. reactive函数

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

javascript 复制代码
import { reactive } from 'vue';

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

1.3. ref函数

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

javascript 复制代码
import { ref } from 'vue';

const count = ref(0);

2. 双向绑定

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

html 复制代码
<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 的使用更加灵活,可以支持自定义组件的双向绑定:

javascript 复制代码
<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 组件中:

html 复制代码
<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 响应式系统的核心代码片段:

javascript 复制代码
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 函数来实现这两个功能。

javascript 复制代码
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 来实现响应式。

javascript 复制代码
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. 编译时实现

javascript 复制代码
// 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. 运行时实现

javascript 复制代码
// 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
}
相关推荐
aiguangyuan3 天前
浅谈 React Hooks
react·前端开发
aiguangyuan4 天前
React Hooks 基础指南
react·前端开发
aiguangyuan5 天前
React 项目初始化与搭建指南
react·前端开发
aiguangyuan5 天前
React 组件异常捕获机制详解
react·前端开发
aiguangyuan6 天前
深入理解 JSX:React 的核心语法
react·前端开发
aiguangyuan6 天前
React 基础语法
react·前端开发
aiguangyuan7 天前
React 核心概念与生态系统
react·前端开发
aiguangyuan7 天前
React 18 生命周期详解与并发模式下的变化
react·前端开发
aiguangyuan9 天前
Vue 3.0 中的路由导航守卫详解
前端开发·vue 3.0
aiguangyuan11 天前
Vue 3.0 状态管理方案Vuex详解
前端开发·vue 3.0