What is two-way binding?
Without further ado, let's take a look at a v-model
basic example:
<input type="text" v-model="search">
First of all, we need to understand that: The essence of v-model
is an instruction . Therefore, it is the same as our general custom instruction and needs to implement the hook function of the Vue.js
life cycle.
Secondly, v-model
realizes two-way binding, that is: one-way flow of data to DOM, and one-way flow of DOM to data .
Understand the above two points, and then look at the code will be much clearer.
// packages/runtime-dom/src/directives/vModel.ts
export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
> = {
created() {},
mounted() {},
beforeUpdate() {}
}
Open the source code of v-model
and we can see that it implements the corresponding Vue.js
life cycle hook function, which is actually a built-in custom instruction.
Then, v-model
how to realize two-way binding? Specifically, how the one-way flow of data to the DOM and the one-way flow of data from the DOM to the data is achieved.
One-way flow of data to the 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
}
}
The one-way flow of data to the DOM is very simple, just one line of code is done, that is, assign the value bound by ---005c10bde5db5d41b0557b058cfabfd8 v-model
to el.value
.
One-way flow of DOM to data
// 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
}
The code is a bit more, but the principle is simple:
- Listen for the
input
element'sinput
orchange
event by custom listener eventaddEventListener
- When the user manually enters data, the corresponding function is executed, and the new value of
input
77b4ba89d1d41ddfa7b4305d579d0b16--- is obtained throughel.value
- Call
el._assign
(onUpdate:modelValue
property corresponding function) methodv-model
binding value
The key to realizing the one-way flow from DOM to data is onUpdate:modelValue
. With the help of Vue 3 Template Explorer , we can view the render
function generated after compilation, and we can find that there is nothing magical about what it does, it is to help us automatically update v-model
on The value of the bound variable.
<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]
])
}
In addition, there are the processing of --- lazy
, the processing of trim
, the processing of numbers, and the problem that the text is cleared when inputting.
About the functions of the two methods onCompositionStart
and onCompositionEnd
, see text added with IME to input that has v-model is gone when the view is updated #2302 .
One sentence summary: One-way flow from DOM to data is achieved by using addEventListener
.
Finally, the implementation of beforeUpdate
, if the value of the data is inconsistent with the value of the DOM, the data is updated to the 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
}
}
The above is the text
type input
element two-way binding principle, of course input
element types are not only this, but also such as radio
checkbox
and other types, if you are interested, you can see it yourself, but the principle is the same, that is, to achieve two functions: one-way flow of data to DOM, and one-way flow of DOM to data .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。