The bidirectional binding of Vue is that data changes can cause interface changes, and interface data changes can also drive data changes.
This feature is actually different from the one-way data flow specification, so it was very attractive to me when I first started to contact Vue. We found that Element UI forms also make heavy use of v-Model bidirectional binding.
Bidirectional binding is not supported by all elements/components. Vue currently supports input, SELECT, checkbox, radio, and components for bidirectional binding using v-model directives.
I had a big problem with bidirectional binding: why doesn’t bidirectional binding cause an update loop? Namely interface change -> data change -> interface change -> data change ->…
v-model
Bidirectional binding of form elements
Since different form elements use different internal directives, we’ll use input as an example. The bidirectional binding principle for other form elements is very similar.
This section deals with instructions and event handling. If it is not clear, I suggest you refer to my previous two articles, otherwise you may have some confusion.
Case analysis
<input v-model="value" />
<div>{{ value }}</div>
setup() {
let value = ref("");
return {
value
};
}
Copy the code
A few simple lines of code implement bidirectional binding between the input form element and the data value.
The code analysis
Let’s look at the render function
import { vModelText as _vModelText, createElementVNode as _createElementVNode, withDirectives as _withDirectives, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock(_Fragment, null, [ _withDirectives(_createElementVNode("input", { "onUpdate:modelValue": $event => ((_ctx.value) = $event) }, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [ [_vModelText, _ctx.value] ]), _createElementVNode("div", null, _toDisplayString(_ctx.value), 1 /* TEXT */), _createTextVNode(" setup() { let value = ref(""); return { value }; }") ], 64 /* STABLE_FRAGMENT */)) } // Check the console for the ASTCopy the code
We analyze the _withDirectives function and see that the INPUT generated VNode uses the _vModelText internal directive and adds a pro function for event handling called onUpdate:modelValue, The onUpdate:modelValue function is used to modify the value;
vModelText
instruction
export const vModelText: ModelDirective< HTMLInputElement | HTMLTextAreaElement > = { created(el, { modifiers: {lazy, trim, number}}, vnode) {// Get vnode.props! [' onUpdate: modelValue] el. The corresponding function _assign = getModelAssigner (vnode) const castToNumber = numbr | | (vnode. Props && Vnode.props. Type === 'number') // If there is a lazy modifier on the input change event, otherwise on the input event addEventListener(el, lazy? 'change' : 'input', e => { let domValue: String | number = el. Value the if (trim) {/ / if there is a trim modifier, DomValue = domValue.trim()} else if (castToNumber) {// If there is a number modifier or if the input type is number, DomValue = toNumber(domValue)} el._assign(domValue)})} beforeUpdate(el, { value, modifiers: {lazy, trim, number}}, vnode) {// Update the function 'onUpdate:modelValue' because it may not update the data, El. _assign = getModelAssigner(vnode) 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 // Update value if (el.value! == newValue) { el.value = newValue } } }Copy the code
created
Hook function, if anylazy
The modifier,input
A form to monitorchangeEvent, otherwise listeninputEvents;beforeUpdate
Hook function, to retrieveonUpdate:modelValue
Function, because the re-render function may change the function, and re-giveinput
The assignment;input
Enter the new content in, if anytrim
Modifier is carried out to remove Spaces, if anynumber
Modifiers orinput
Type isnumber
The type needs to be converted tonumberAnd then throughonUpdate:modelValue
Corresponding function modificationvalue
Value.
Conclusion:
- Data ->DOM: reactive data
value
Changes trigger component updates,inputContent will find changes;- DOM – > data:
vModelText
The directive implements a pair ofinputthevalue
Change of monitor, according tovModelText
The modifier of the instruction is finishedinputthevalue
Value, and then passonUpdate:modelValue
Corresponding function$event => (value = $event)
To complete reactive data againvalue
Modifications. Changes to reactive data trigger component updates.
Think about it
Why wouldn’t there be an update loop
Input input data -> data processing -> Call onUpdate:modelValue corresponding $event => (inputValue = $event) method -> Reactive data change component update -> INPUT Settings update input.value = The newValue update stops at this point.
Why updateinput
The new value of thevNodelText
The directivebeforeUpdate
To perform?
Directives are updated in two ways: beforeUpdate and updated. Executing in beforeUpdate has two advantages:
- Update the DOM before updating it
input
New value of, if only modifiedinput
The value of this is eliminatedpatchProp
Part of the operation has been improvedpatch
Performance. - The directive
beforeUpdate
Is before DOM update, andupdated
The hook function is updated in the DOMAsynchronous executionIf there are too many synchronization tasks in complex services, update delay or lag may occur.
v-model
Bidirectional binding of components
<Son v-model="modalValue"/>
Copy the code
It’s the same thing
<Son :modalValue="modalValue" @update:modalValue="modalUpdate=$event.target.value"/>
Copy the code
The v-model bidirectional binding of components is essentially a syntax sugar, passing data to child components through prop, which can modify data through v-ON time binding
If you want to customize the Model parameters
<Son v-model:visible="visible"/>
setup(props, ctx){
ctx.emit("update:visible", false)
}
Copy the code