Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >v-model数据绑定分析

v-model数据绑定分析

原创
作者头像
WindRunnerMax
修改于 2020-12-01 06:39:49
修改于 2020-12-01 06:39:49
2K0
举报
文章被收录于专栏:Czy‘s BlogCzy‘s Blog

v-model数据绑定分析

v-modelVue提供的指令,其主要作用是可以实现在表单<input><textarea><select>等元素以及组件上创建双向数据绑定,其本质上就是一种语法糖,既可以直接定义在原生表单元素,也可以支持自定义组件。在组件的实现中,可以配置子组件接收的prop名称,以及派发的事件名称实现组件内的v-model双向绑定。

描述

可以用v-model指令在表单<input><textarea><select>元素上创建双向数据绑定,其会根据控件类型自动选取正确的方法来更新元素,以<input>作为示例使用v-model

代码语言:txt
AI代码解释
复制
<!DOCTYPE html>

<html>

<head>

    <title>Vue</title>

</head>

<body>

    <div id="app"></div>

</body>

<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>

<script type="text/javascript">

    var vm = new Vue({

        el: "#app",

        data: {

            msg: ""

        },

        template: `

            <div>

                <div>Message is: {{ msg }}</div>

                <input v-model="msg">

            </div>

        `

    })

</script>

</html>

当不使用v-model语法糖时,可以自行实现一个双向绑定,实际上v-model在内部为不同的输入元素使用不同的property并抛出不同的事件:

* inputtextarea元素使用value propertyinput事件。

* checkboxradio元素使用checked propertychange事件。

* select元素将value作为prop并将change作为事件。

同样以<input>作为示例而不使用v-model实现双向绑定。

代码语言:txt
AI代码解释
复制
<!DOCTYPE html>

<html>

<head>

    <title>Vue</title>

</head>

<body>

    <div id="app"></div>

</body>

<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>

<script type="text/javascript">

    var vm = new Vue({

        el: "#app",

        data: {

            msg: ""

        },

        template: `

            <div>

                <div>Message is: {{ msg }}</div>

                <input :value="msg" @input="msg = $event.target.value">

            </div>

        `

    })

</script>

</html>

对于v-model还有修饰符用以控制用户输入:

* .trim: 输入首尾空格过滤。

* .lazy: 取代input事件而监听change事件。

* .number: 输入字符串转为有效的数字,如果这个值无法被parseFloat()解析,则会返回原始的值。

代码语言:txt
AI代码解释
复制
<!DOCTYPE html>

<html>

<head>

    <title>Vue</title>

</head>

<body>

    <div id="app"></div>

</body>

<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>

<script type="text/javascript">

    var vm = new Vue({

        el: "#app",

        data: {

            msg: 0

        },

        template: `

            <div>

                <div>Message is: {{ msg }}</div>

                <div>Type is: {{ typeof(msg) }}</div>

                <input v-model.number="msg" type="number">

            </div>

        `

    })

</script>

</html>

当使用自定义组件时,在组件上的v-model默认会利用名为valueprop和名为input的事件,但是像单选框、复选框等类型的输入控件可能会将value attribute用于不同的目的,此时可以使用model选项可以用来避免这样的冲突。

代码语言:txt
AI代码解释
复制
<!DOCTYPE html>

<html>

<head>

    <title>Vue</title>

</head>

<body>

    <div id="app"></div>

</body>

<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>

<script type="text/javascript">

    Vue.component("u-input", {

        model: {

            prop: "message",

            event: "input"

        },

        props: {

            message: { 

                type: String

            },

        },

        template: `

            <div>

                <input :value="message" @input="$emit('input', $event.target.value)">

            </div>

        `

    })

    var vm = new Vue({

        el: "#app",

        data: {

            msg: ""

        },

        template: `

            <div>

                <div>Message is: {{ msg }}</div>

                <u-input v-model="msg"></u-input>

            </div>

        `

    })

</script>

</html>

分析

Vue源码的实现比较复杂,会处理各种兼容问题与异常以及各种条件分支,文章分析比较核心的代码部分,精简过后的版本,重要部分做出注释,commit idef56410

v-model属于Vue的指令,所以从编译阶段开始分析,在解析到指令之前,Vue的解析阶段大致流程:解析模版字符串生成AST、优化语法树AST、生成render字符串。

代码语言:txt
AI代码解释
复制
// dev/src/compiler/index.js line 11

export const createCompiler = createCompilerCreator(function baseCompile (

  template: string,

  options: CompilerOptions

): CompiledResult {

  const ast = parse(template.trim(), options) // 生成AST

  if (options.optimize !== false) {

    optimize(ast, options) // 优化AST

  }

  const code = generate(ast, options) // 生成代码 即render字符串

  return {

    ast,

    render: code.render,

    staticRenderFns: code.staticRenderFns

  }

})

对指令的处理就在生成render字符串的过程,也就是generate函数的处理过程,在generate中调用genElement -> genData -> genDirectives,文章主要从genDirectives函数进行分析。

代码语言:txt
AI代码解释
复制
// dev/src/compiler/codegen/index.js line 43

export function generate (

  ast: ASTElement | void,

  options: CompilerOptions

): CodegenResult {

  const state = new CodegenState(options)

  const code = ast ? genElement(ast, state) : '\_c("div")'

  return {

    render: `with(this){return ${code}}`, // render字符串

    staticRenderFns: state.staticRenderFns

  }

}



// dev/src/compiler/codegen/index.js line 55

export function genElement (el: ASTElement, state: CodegenState): string {

  // ...

  data = genData(el, state)

  // ...

}



// dev/src/compiler/codegen/index.js line 219

export function genData (el: ASTElement, state: CodegenState): string {

  // ...

  const dirs = genDirectives(el, state)

  // ...

}

在生成AST阶段,也就是parse阶段,v-model被当做普通的指令解析到el.directives中,genDrirectives方法就是遍历el.directives,然后获取每一个指令对应的方法,对于v-model而言,在此处获取的是{name: "model", rawName: "v-model" ...},通过state找到model指令对应的方法model()并执行该方法。

代码语言:txt
AI代码解释
复制
// dev/src/compiler/codegen/index.js line 309

function genDirectives (el: ASTElement, state: CodegenState): string | void {

  const dirs = el.directives // 获取指令

  if (!dirs) return

  let res = 'directives:['

  let hasRuntime = false

  let i, l, dir, needRuntime

  for (i = 0, l = dirs.length; i < l; i++) { // 遍历指令

    dir = dirs[i]

    needRuntime = true

    const gen: DirectiveFunction = state.directives[dir.name] // 对于v-model来说 const gen = state.directives["model"];

    if (gen) {

      // compile-time directive that manipulates AST.

      // returns true if it also needs a runtime counterpart.

      needRuntime = !!gen(el, dir, state.warn)

    }

    if (needRuntime) {

      hasRuntime = true

      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${

        dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''

      }${

        dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''

      }${

        dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''

      }},`

    }

  }

  if (hasRuntime) {

    return res.slice(0, -1) + ']'

  }

}

model方法主要是根据传入的参数对tag的类型进行判断,调用不同的处理逻辑。

代码语言:txt
AI代码解释
复制
// dev/src/platforms/web/compiler/directives/model.js line 14

export default function model (

  el: ASTElement,

  dir: ASTDirective,

  \_warn: Function

): ?boolean {

  warn = \_warn

  const value = dir.value

  const modifiers = dir.modifiers

  const tag = el.tag

  const type = el.attrsMap.type



  if (process.env.NODE\_ENV !== 'production') {

    // inputs with type="file" are read only and setting the input's

    // value will throw an error.

    if (tag === 'input' && type === 'file') {

      warn(

        `<${el.tag} v-model="${value}" type="file">:\n` +

        `File inputs are read only. Use a v-on:change listener instead.`,

        el.rawAttrsMap['v-model']

      )

    }

  }

    

  // 分支处理

  if (el.component) {

    genComponentModel(el, value, modifiers)

    // component v-model doesn't need extra runtime

    return false

  } else if (tag === 'select') {

    genSelect(el, value, modifiers)

  } else if (tag === 'input' && type === 'checkbox') {

    genCheckboxModel(el, value, modifiers)

  } else if (tag === 'input' && type === 'radio') {

    genRadioModel(el, value, modifiers)

  } else if (tag === 'input' || tag === 'textarea') {

    genDefaultModel(el, value, modifiers)

  } else if (!config.isReservedTag(tag)) {

    genComponentModel(el, value, modifiers)

    // component v-model doesn't need extra runtime

    return false

  } else if (process.env.NODE\_ENV !== 'production') {

    warn(

      `<${el.tag} v-model="${value}">: ` +

      `v-model is not supported on this element type. ` +

      'If you are working with contenteditable, it\'s recommended to ' +

      'wrap a library dedicated for that purpose inside a custom component.',

      el.rawAttrsMap['v-model']

    )

  }



  // ensure runtime directive metadata

  return true

}

genDefaultModel函数先处理了modifiers修饰符,其不同主要影响的是eventvalueExpression的值,对于<input>标签eventinputvalueExpression$event.target.value,然后去执行genAssignmentCode去生成代码,以及添加属性值与事件处理。

代码语言:txt
AI代码解释
复制
// dev/src/platforms/web/compiler/directives/model.js line 127

function genDefaultModel (

  el: ASTElement,

  value: string,

  modifiers: ?ASTModifiers

): ?boolean {

  const type = el.attrsMap.type



  // warn if v-bind:value conflicts with v-model

  // except for inputs with v-bind:type

  // value与v-model冲突则发出警告

  if (process.env.NODE\_ENV !== 'production') {

    const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']

    const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']

    if (value && !typeBinding) {

      const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'

      warn(

        `${binding}="${value}" conflicts with v-model on the same element ` +

        'because the latter already expands to a value binding internally',

        el.rawAttrsMap[binding]

      )

    }

  }



  // 修饰符处理

  const { lazy, number, trim } = modifiers || {}

  const needCompositionGuard = !lazy && type !== 'range'

  const event = lazy

    ? 'change'

    : type === 'range'

      ? RANGE\_TOKEN

      : 'input'



  let valueExpression = '$event.target.value'

  if (trim) {

    valueExpression = `$event.target.value.trim()`

  }

  if (number) {

    valueExpression = `\_n(${valueExpression})`

  }



  let code = genAssignmentCode(value, valueExpression)

  if (needCompositionGuard) {

    code = `if($event.target.composing)return;${code}`

  }



  addProp(el, 'value', `(${value})`)

  addHandler(el, event, code, null, true)

  if (trim || number) {

    addHandler(el, 'blur', '$forceUpdate()')

  }

}



// dev/src/compiler/directives/model.js line 36

export function genAssignmentCode (

  value: string,

  assignment: string

): string {

  const res = parseModel(value)

  if (res.key === null) {

    return `${value}=${assignment}`

  } else {

    return `$set(${res.exp}, ${res.key}, ${assignment})`

  }

}

每日一题

代码语言:txt
AI代码解释
复制
https://github.com/WindrunnerMax/EveryDay

参考

代码语言:txt
AI代码解释
复制
https://cn.vuejs.org/v2/api/#v-model

https://www.jianshu.com/p/19bb4912c62a

https://www.jianshu.com/p/0d089f770ab2

https://cn.vuejs.org/v2/guide/forms.html

https://juejin.im/post/6844903784963899400

https://juejin.im/post/6844903999414485005

https://segmentfault.com/a/1190000021516035

https://segmentfault.com/a/1190000015848976

https://github.com/haizlin/fe-interview/issues/560

https://ustbhuangyi.github.io/vue-analysis/v2/extend/v-model.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
vue源码分析-v-model的本质
由于v-model和前面介绍的插槽,事件一致,都属于vue提供的指令,所以我们对v-model的分析方式和以往大同小异。分析会围绕模板的编译,render函数的生成,到最后真实节点的挂载顺序执行。最终我们依然会得到一个结论,v-model无论什么使用场景,本质上都是一个语法糖。
yyzzabc123
2022/10/18
1K0
[咖聊] “模板编译”真经
冲一杯美式 ☕️ ,读编译真经,岂不快哉? 本文的 🍪 (表示 例子,☕️ 和 🍪 更配哦!全文都会围绕这个 DEMO 做解析。⚠️ 因不能直接跳转到外链,注意有标注的地方,文末有对应的地址哦,跳转着看更容易理解哦!): <div id="app"> <!-- 这是一个注释节点 --> <Child name="yjc" :age="12" v-if="isShow"></Child> <input type="text" v-model="inputValue" /> <d
码农小余
2022/06/16
1.1K0
[咖聊] “模板编译”真经
vue用v-on动态绑定事件名需要2.6以上版本才有效
vue 项目中需要在子组件中动态接收父组件传递的事件名来监听,直接用 v-on 或者 @ 符号加上中括号[]动态绑定事件名是可以实现的,一个简单的 demo 如下:
人人都是码农
2023/11/16
2720
【Vue原理】Compile - 源码版 之 generate 节点数据拼接
【Vue原理】Compile - 源码版 之 generate 节点数据拼接
神仙朱
2019/08/02
7070
【Vue原理】Compile - 源码版 之 generate 节点数据拼接
vue 中使用 v-bind 与 v-on 来实现 v-model 双向绑定 ?
v-model其实是一个语法糖,他的背后本质包含两个操作: 1.v-bind绑定一个value属性 2.v-on指令给当前元素绑定input事件
青梅煮码
2023/01/16
7240
vue 中使用 v-bind 与 v-on 来实现 v-model 双向绑定 ?
9. Vue 使用 v-model 实现双向数据绑定
在Vue框架中,能够绑定表单元素数据的命令有v-bind和v-model,但是v-bind只能单向绑定(将data中的数据绑定到View视图中),而v-model则可以双向绑定(也就是View视图与数据M双向绑定改变)。
Devops海洋的渔夫
2020/03/19
8500
Vue(v2.6.11)万行源码生啃,就硬刚!
众所周知,以下代码就是 vue 的一种直接上手方式。通过 cdn 可以在线打开 vue.js。一个文件,一万行源码,是万千开发者赖以生存的利器,它究竟做了什么?让人品味。
掘金安东尼
2024/01/27
4280
Vue(v2.6.11)万行源码生啃,就硬刚!
【Vue原理解析】之模版编译
Vue.js是一款流行的JavaScript框架,它采用了基于组件的开发模式,使得前端开发更加简单和高效。而Vue的核心功能之一就是模版解析,它负责将Vue组件中的模版代码转化为可执行的JavaScript代码。本文将深入探讨Vue模版解析的作用、核心源码分析以及总结。
can4hou6joeng4
2023/11/15
2240
vue为什么v-for的优先级比v-if的高?_2023-03-13
有时候有些面试中经常会问到v-for与v-if谁的优先级高,这里就通过分析源码去解答一下这个问题。
用户10358241
2023/03/13
3730
【Vue原理】VModel - 源码版 之 表单元素绑定流程
v-model 涉及源码很多,篇幅很长,我都已经分了上下 三篇了,依然这么长,但是其实内容都差不多一样,但是我还是毫无保留地给你了。你知道我这篇文章写了多久,一个多星期啊,不是研究多久啊,是写啊写啊,不停地修修改改,一直在想如何才能讲明白
神仙朱
2019/08/02
8480
【Vue原理】VModel - 源码版 之 表单元素绑定流程
美团前端常见vue面试题(必备)_2023-02-28
(1)作用在表单元素上 动态绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动态把 message设置为目标值:
用户10377014
2023/02/28
7500
【Vue原理】Compile - 源码版 之 属性解析
哈哈哈,今天终于到了属性解析的部分了,之前已经讲过了 parse 流程,标签解析,最后就只剩下 属性解析了 (´・ᴗ・`)
神仙朱
2019/08/02
9980
【Vue原理】Compile - 源码版 之 属性解析
一文搞定Vue面试
mutation中的操作是一系列的同步函数,用于修改state中的变量的的状态。当使用vuex时需要通过commit来提交需要操作的内容。mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
bb_xiaxia1998
2022/11/02
6480
Vue事件绑定原理
Vue中通过v-on或其语法糖@指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成AST,生成render函数后并执行得到VNode,VNode生成真实DOM节点或者组件时候使用addEventListener方法进行事件绑定。
WindRunnerMax
2020/09/10
9K0
# Vue 模板编译原理解析
在 Vue 开发过程中,我们通常使用.vue文件进行开发,然后上线时打包成一个js最后在页面中加载然后渲染 DOM。
九旬
2023/10/18
4070
# Vue 模板编译原理解析
「.vue文件的编译」3. 模板编译之AST生成
下面parseHTML方法是用来遍历html字符串的并解析出标签(当然包含标签中的属性)、文本等信息,详细分析参考这里。
tinyant
2023/02/24
1.3K0
vue.js中的v-model指令的深刻理解
vue中经常使用到和这类表单元素,vue对于这些元素的数据绑定和我们以前经常用的jQuery有些区别。vue使用v-model实现这些标签数据的双向绑定,它会根据控件类型自动选取正确的方法来更新元素。
张哥编程
2024/12/13
1720
vue.js中的v-model指令的深刻理解
前端一面常见vue面试题汇总_2023-02-27
eventBus事件总线适用于父子组件、非父子组件等之间的通信,使用步骤如下: (1)创建事件中心管理组件之间的通信
用户10358241
2023/02/27
8090
VUE之v-model指令
以下实例判断 gray的值,如果为 true 使用 gray类的样式,否则不使用该类。但是什么是v-model呢?并没有给出提示。
张哥编程
2024/12/19
1340
VUE之v-model指令
熬夜整理的vue面试题
vue-router 有 3 种路由模式:hash、history、abstract,对应的源码如下所示
bb_xiaxia1998
2022/11/16
8080
相关推荐
vue源码分析-v-model的本质
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档