什么是双向绑定?

废话不多说,我们先来看一个 v-model 基本的示例:

<input type="text" v-model="search">

首先,我们要明白一点的是:v-model 的本质是指令。因此,它跟我们一般的自定义指令是一样的,需要实现 Vue.js 生命周期的钩子函数。

其次,v-model 实现了双向绑定,也就是:数据到 DOM 的单向流动DOM 到数据的单向流动

明白了上面这两点,再来看代码就清晰多了。

// packages/runtime-dom/src/directives/vModel.ts

export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
> = {
created() {},
mounted() {},
beforeUpdate() {}
}

打开 v-model 的源码我们可以看到,它实现了对应的 Vue.js 生命周期钩子函数,实际上它就是一个内置的自定义指令。

那么,v-model 如何实现双向绑定的呢?具体来说,数据到 DOM 的单向流动以及DOM 到数据的单向流动是如何实现的。

数据到 DOM 的单向流动

// packages/runtime-dom/src/directives/vModel.ts

export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
> = {
// set value on mounted so it's after min/max for type="range"
mounted(el, { value }) {
el.value = value == null ? '' : value
}
}

数据到 DOM 的单向流动实现非常简单,一行代码就搞定了,就是把 v-model 绑定的值赋值给 el.value

DOM 到数据的单向流动

// packages/runtime-dom/src/directives/vModel.ts

export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
> = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode) // see: https://github.com/vuejs/core/issues/3813
const castToNumber = number || (vnode.props && vnode.props.type === 'number') // 实现 lazy 功能
addEventListener(el, lazy ? 'change' : 'input', e => {
// `composing=true` 时不把 DOM 的值赋值给数据
if ((e.target as any).composing) return let domValue: string | number = el.value
if (trim) {
domValue = domValue.trim()
} else if (castToNumber) {
domValue = toNumber(domValue)
} // DOM 的值改变时,同时改变对应的数据(即改变 v-model 上绑定的变量的值)
el._assign(domValue)
}) // 实现 trim 功能
if (trim) {
addEventListener(el, 'change', () => {
el.value = el.value.trim()
})
} // 不配置 lazy 时,监听的是 input 的 input 事件,它会在用户实时输入的时候触发。
// 此外,还会多监听 compositionstart 和 compositionend 事件。
if (!lazy) {
// 这是因为,用户使用拼音输入法开始输入汉字时,这个事件会被触发,
// 此时,设置 `composing=true`,在 input 事件回调里可以进行判断,避免将 DOM 的值赋值给数据,
// 因为此时并未输入完成。
addEventListener(el, 'compositionstart', onCompositionStart) // 当用户从输入法中确定选中了一些数据完成输入后(如中文输入法常见的按空格确认输入的文字),
// 设置 `composing=false`,在 onCompositionEnd 中手动触发 input 事件,完成数据的赋值。
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)
}
}
} 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
target.dispatchEvent(new Event('input'))
}
} const getModelAssigner = (vnode: VNode): AssignerFn => {
const fn = vnode.props!['onUpdate:modelValue']
return isArray(fn) ? value => invokeArrayFns(fn, value) : fn
}

代码有点多,但原理很简单:

  • 通过自定义监听事件 addEventListener 来监听 input 元素的 inputchange 事件
  • 当用户手动输入数据时执行对应的函数,并通过 el.value 获取 input 的新值
  • 调用 el._assignonUpdate:modelValue 属性对应的函数)方法 v-model 绑定的值

而实现 DOM 到数据的单向流动,关键就在 onUpdate:modelValue。借助 Vue 3 Template Explorer,我们可以查看其编译后生成的 render 函数,可以发现它做所的事情并没有什么神奇的地方,就是帮我们自动更新 v-model 上绑定的变量的值。

<input type="text" v-model="search">

import { vModelText as _vModelText, withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
return _withDirectives((_openBlock(), _createElementBlock("input", {
type: "text", // `onUpdate:modelValue` 所做的事,
// 就是自动帮我们更新 `v-model` 上绑定的变量的值。
"onUpdate:modelValue": $event => ((_ctx.search) = $event) }, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
[_vModelText, _ctx.search]
])
}

除此之外,还有对 lazy 的处理、trim 的处理、数字的处理、以及解决正在输入时文本被清空的问题。

关于 onCompositionStartonCompositionEnd 两个方法的作用,详见 text added with IME to input that has v-model is gone when the view is updated #2302

一句话总结:通过使用 addEventListener 来实现 DOM 到数据的单向流动

最后是 beforeUpdate 的实现,如果数据的值和 DOM 的值不一致,则将数据更新到 DOM:

// packages/runtime-dom/src/directives/vModel.ts

beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode)
// avoid clearing unresolved text. #2302
// 输入某些语言如中文,在没有输入完成时,在更新时会自动将已存在的文本清空,具体可见 issue#2302
if ((el as any).composing) return if (document.activeElement === el) {
if (lazy) {
return
}
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
}
}

以上就是 text 类型的 input 元素双向绑定原理,当然 input 元素类型不止这个,还有诸如 radiocheckbox 等类型,大家有兴趣的话可以自己去看,但是原理都是相同的,就是实现两个功能:数据到 DOM 的单向流动DOM 到数据的单向流动

Vue.js 3.x 双向绑定原理的更多相关文章

  1. vue的双向绑定原理及实现

    前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...

  2. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

  3. Vue双向绑定原理,教你一步一步实现双向绑定

    当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...

  4. vue双向绑定原理分析

    当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/jiangzhenf ...

  5. Vue之双向绑定原理动手记

    Vue.js的核心功能有两个:一是响应式的数据绑定系统,二是组件系统.本文是通过学习他人的文章,从而理解了双向绑定原理,从而在自己理解的基础上,自己动手实现数据的双向绑定. 目前几种主流的mvc(vm ...

  6. vue数据双向绑定原理

    vue的数据双向绑定的小例子: .html <!DOCTYPE html> <html> <head> <meta charset=utf-> < ...

  7. vue双向绑定原理及实现

    vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...

  8. Vue 双向绑定原理

    Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统. 一.访问器属性:Object.defineProperty ECMAScript 262v5带来的新东西,FF把它归入为jav ...

  9. vue的双向绑定原理解析(vue项目重构二)

    现在的前端框架 如果没有个数据的双向/单向绑定,都不好意思说是一个新的框架,至于为什么需要这个功能,从jq或者原生js开始做项目的前端工作者,应该是深有体会. 以下也是个人对vue的双向绑定原理的一些 ...

随机推荐

  1. Linux如何查看某个端口是否被占用

    1.netstat  -anp  |grep   端口号 2.netstat   -nultp(此处不用加端口号) 3.netstat  -anp  |grep  82    查看82端口的使用情况

  2. MyBatis Plus 2.3 个人笔记-03-Active Record

    AR 语法糖  是一种领域模型模式,特点就是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一条记录 实现AR [在代码生成器中可以添加配置] import com.baomidou ...

  3. buuctf 荷兰带宽数据泄露

    荷兰带宽数据泄露 下载附件得一个conf.bin文件,这个文件是路由信息文件,题目并没有任何提示,我们先来测试一下最简单的,找username或password然后当作flag交上去,我们使用Rout ...

  4. (4)_结果与讨论Result and Discussion【论文写作】

  5. dll反编译(修改引用文件、修改代码)再生成dll

    问题描述 我们在日常开发中经常会遇到,想要对dll文件做修改的操作,但苦于没有源代码,只能想想其他办法 解决问题 办法就是通过几个工具来反编译.正向编译.修改属性 反编译.正编译 参考https:// ...

  6. 编写大型项目web页面 从写web登陆页面开始

    web页面搭建需要准备什么工具 首先我们会和设计师沟通 我们需要一些检验设计的工具 ps 自动裁图 自动测量工具 (我这里安利一下一个工具 我用的cutterman) sketch 可以使用阿里的工具 ...

  7. 设计模式之简单工厂SimpleFactory的实现(配置文件)

    <?xml version="1.0" encoding="utf-8" ?> <configuration> <appSetti ...

  8. CSS简单样式练习(五)

    运行效果: 源代码: 1 <!DOCTYPE html> 2 <html lang="zh"> 3 <head> 4 <meta char ...

  9. nodejs教程---基于expressJs框架,实现文件上传(upload)?

    文件上传功能在nodejs初期是一件很难实现的功能,之后出现了formidable勉强能解决这个问题,但是express框架出现之后基于这个框架开发的中间件有更好的方法来处理文件上传,这个中间件就是m ...

  10. 基于express框架的留言板实现步骤

    这个留言板是基于express框架,和ejs模板引擎,首先需要在根目录安装express框架,然后安装ejs模块和body-parser(获取用户表单提交的数据):建立项目目录 message,然后依 ...