I was experimenting with Vue's v-model
the other day. I wanted to figure out if what the Vue docs state about v-model
was true. Here's what they say:
Remember that:
<input v-model="searchText">
does the same thing as:
<input v-bind:value="searchText" v-on:input="searchText = $event.target.value">
This phrase says that one can rebuild v-model
by setting the value
prop of a form control (any, really) and setting the prop to the target value in an event listener for the input
event. Simple enough, right?
And at first glance, that's very much true. They do the same thing. Updating the prop from somewhere else updates whatever's written in the input field, and updating the input field updates the prop.
Let's look at the generated code, though, to verify that it's really, actually the same.
The example app we'll work with is the most basic example we can build for this:
<template>
<div>
<input
v-model="someProp"
>
{{ someProp }}
</div>
</template>
<script>
export default {
data() {
return {
someProp: 'Foobar',
}
}
}
</script>
A simple template with an input field and a text node, the script provides the text with some default value.
Next, we execute npm run build
and open up the generated dist/js/app.something.js
. Ugh, minified. But nothing my IDE can't handle. Doing a quick Ctrl+F for someProp
will show us the relevant part of the code:
return n("div", [n("input", {
directives: [{
name: "model",
rawName: "v-model",
value: e.someProp,
expression: "someProp"
}],
domProps: { value: e.someProp },
on: {
input: function (t) {
t.target.composing || (e.someProp = t.target.value)
}
}
}) // ... ])
We see here the component from above but expressed as render functions. The interesting parts are the domProps
and the on
keys. They tell us how v-model
generated the two-way binding.
Now, let's rebuild the above component to use :value
and @input
instead:
<template>
<div>
<input
:value="someProp"
@input="e => someProp = e.target.value"
>
{{ someProp }}
</div>
</template>
<script>
export default {
data() {
return {
someProp: 'Foobar',
}
}
}
</script>
And do the same shenanigans as before to get the generated code:
return n("div", [n("input", {
domProps: { value: e.someProp },
on: {
input: function (t) {
return e.someProp = t.target.value
}
}
}) // ... ])
Notice the difference? The directives
key is missing. That's not that surprising, we don't use the v-model
directive after all, right? The only difference in functionality is the missing t.target.composing ||
in the "self-made" version. We can add that, though:
<input
:value="someProp"
@input="e => e.target.composing || (someProp = e.target.value)"
>
What we'll get is this:
return n("div", [n("input", {
domProps: { value: e.someProp },
on: {
input: function (t) {
t.target.composing || (e.someProp = t.target.value)
}
}
}) // ... ])
And that's the equivalent to v-model
. Without the directives
key, though.
And that's the point.
Using the @input
+:value
approach makes the built JS around 85 bytes smaller (estimate, it may vary as it's dependent on the variable's name, the minimum is 71) per usage of v-model
.
Now imagine our app has 100 usages of v-model
, that'll be roughly 8.5kb less built JS.
The price is readability, though. Who wants to bloat their templates with event listeners and bindings if they could just use v-model
, right? Also, the gain is minimal compared to the amount of annoyance this can cause. So, we probably shouldn't do this.
β I also want to shout out to Andras for supporting me via buymeacoffee! I enjoyed the coffee in the sun on my favorite bench - Just the right thing to relax after a long day. Thank you!
I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a β€οΈ or a π¦! I write tech articles in my free time and like to drink coffee every once in a while.
If you want to support my efforts, you can offer me a coffee β or follow me on Twitter π¦! You can also support me directly via Paypal!
Top comments (4)
This seems like something the Vue compiler should handle for the user
Oh, absolutely! That's why I called it "silly" - it's that kind of micro optimization that shouldn't really be necessary, especially if it's syntactic sugar causing the extra 71 bytes.
This is a nice approach to understanding what really happens under the hood Pascal.
Nice one πͺ
Yeah! Good to raise the fact that it slightly decreases redability! Unfortunately, due to the figures,.. it won't stop me from refactoring my code