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-modelBidirectional 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

In just a few lines of code, the input form element and the data value are bidirectionally bound.

The code analysis

Let’s look at the render function
const _hoisted_1 = ["onUpdate:modelValue"]

_withDirectives(_createElementVNode("input", {
  "onUpdate:modelValue": $event => (value = $event)
}, null, 8 /* PROPS */, _hoisted_1), [
  [_vModelText, value]
])
Copy the code

We analyze the withDirectives function and see that the INPUT generated VNode uses the internal directive vModelText and adds a pro function for event handling called onUpdate:modelValue, The onUpdate:modelValue function is used to modify the value;

vModelTextinstruction
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 = number | | (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
  1. createdHook function, if anylazyThe modifier,inputA form to monitorchangeEvent, otherwise listeninputEvents;
  2. beforeUpdateHook function, to retrieveonUpdate:modelValueFunction, because the re-render function may change the function, and re-giveinputThe assignment;
  3. inputEnter the new content in, if anytrimModifier is carried out to remove Spaces, if anynumberModifiers orinputType isnumberThe type needs to be converted tonumberAnd then throughonUpdate:modelValueCorresponding function modificationvalueValue.

Conclusion:

  1. Data ->DOM: reactive datavalueChanges trigger component updates,inputContent will find changes;
  2. DOM – > data:vModelTextThe directive implements a pair ofinputthevalueChange of monitor, according tovModelTextThe modifier of the instruction is finishedinputthevalueValue, and then passonUpdate:modelValueCorresponding function$event => (value = $event)To complete reactive data againvalueModifications. Changes to reactive data trigger component updates.

Some think

Why wouldn’t there be an update loop?

Input input data -> data processing -> call onUpdate:modelValue corresponding $event => (inputValue = $event) method -> Reactive data changes trigger component update -> INPUT sets the new value input.value = newValue Update terminates at this point.

Why updateinputThe new value of thevModelTextThe directivebeforeUpdateTo perform?

Directives have two methods for updating: beforeUpdate and updated. Executing in beforeUpdate has two advantages:

  1. Update the DOM before updating itinputNew value of, if only modifiedinputThe value of this is eliminatedpatchPropPart of the operation has been improvedpatchPerformance;
  2. The directivebeforeUpdateIs performed synchronously before DOM update, andupdatedThe hook function is updated in the DOMAsynchronous executionIf there are too many synchronization tasks in a complex business, update delay or lag may occur.

v-modelBidirectional binding of components

<Son v-model="modelVlue" />
Copy the code

In fact, it is equivalent to:

<Son
  :modelValue="modelVlue"
  @update:modelValue="modelVlue = $event"
></Son>
Copy the code

The v-model bidirectional binding of components is essentially a syntax sugar, passing data to the child components via Pro, and the child components can modify the data through v-ON event binding.