There is a simpler way to achieve this, which is probably even less known, without the use of an extra component and it goes like this:
<liv-for="id in users":key="id":set="item = getUserData(id)"><img:src="item.avatar"/><br/>🏷️{{item.name}}<br/>🔗{{item.homepage}}</li>
For those that may now be thinking, "Hey, I didn't know there was an undocumented :set in Vue", there isn't. What I'm doing here is relying on the fact that Vue will evaluate the JavaScript of any bound attributes and I just chose to invent an attribute called :set.
As a reminder, a bound attribute is one that is prefixed with a : or v-bind:. The JavaScript expression inside the double quotes will be evaluated in the context of the current component and the item variable will still be available outside of the v-for in which it is being set. So, it's not a local variable, as such, rather, it's a locally assigned component scope (non-reactive) variable.
Do note that this attribute does not have to be declared in your data component first. However, if you don't declare it, it will not be reactive. In the example above it does not matter to me if it's reactive, but it's something to keep in mind if you use this pattern
I think my perception of this as a "hack" mostly refers to "using assignment expressions as attributes". It just doesn't feel right (to me, personally). Sure, it's a thing we've done forever in on* event attributes, but at least the semantics of events do match this better than the semantics of vanilla attributes.
That's pretty much the same as let foo; while (foo = expression) { /* use foo */ } — it's a thing that is frowned upon by many readers and linters, but it's used because the clean way would be annoyingly verbose.
It doesn’t. It just helps to avoid using deeply nested object references or making calls to the same function several times. You could use a computed, without a parameter, if you want caching.
Indeed, you’re right. A normal computed wouldn’t have access to the v-for parameter here and a parametrized computed wouldn’t be cached.
If you need caching then you will need a different technique.
You could declare a data variable and then assign the v-for iterator to it, like I have shown in my example. In this way, a computed would have access to that data variable with the iterator’s value. Would this work for you?
This may work in JS but TypeScript can't make heads or tails of this. The referenced function will be marked as un-used, causing a TS error, and the unknown variable will also be marked as unknown. You can define the var in the <script> block, but it will still leave the previous error.
Repeated calls to getUserData within the loop, then, will instantly return the item, and once we're out of that iteration of the loop, the cached result will be garbage collected.
I didn't put in TypeScript annotations but TS handles this approach much better as well.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
There is a simpler way to achieve this, which is probably even less known, without the use of an extra component and it goes like this:
For those that may now be thinking, "Hey, I didn't know there was an undocumented
:set
in Vue", there isn't. What I'm doing here is relying on the fact that Vue will evaluate the JavaScript of any bound attributes and I just chose to invent an attribute called:set
.As a reminder, a bound attribute is one that is prefixed with a
:
orv-bind:
. The JavaScript expression inside the double quotes will be evaluated in the context of the current component and theitem
variable will still be available outside of thev-for
in which it is being set. So, it's not a local variable, as such, rather, it's a locally assigned component scope (non-reactive) variable.Do note that this attribute does not have to be declared in your data component first. However, if you don't declare it, it will not be reactive. In the example above it does not matter to me if it's reactive, but it's something to keep in mind if you use this pattern
Here is a fork of Florian's code (thank's Florian) showing the pattern at work.
codesandbox.io/s/6nwyw3zzwz
This is definitely a clever hack. A little bit dirty, but absolutely clever. 😁
Thanks. I would like to add, though, this is neither dirty nor a hack.
Vue does allow us to create local variables on
this
and just cautions that they won't be reactive (I read it somewhere in the docs).As for setting values, well, it's just like an event handler, where one would do exactly the same:, for example:
@click="item=$event"
I know it feels like a hack, at first looks, because it's too simple, but it's legit.
Perhaps, I cheated a bit by using an attribute named
:set
;)I think my perception of this as a "hack" mostly refers to "using assignment expressions as attributes". It just doesn't feel right (to me, personally). Sure, it's a thing we've done forever in
on*
event attributes, but at least the semantics of events do match this better than the semantics of vanilla attributes.That's pretty much the same as
let foo; while (foo = expression) { /* use foo */ }
— it's a thing that is frowned upon by many readers and linters, but it's used because the clean way would be annoyingly verbose.I hear you.
How does this solve the fact that the call is not memoized?
It doesn’t. It just helps to avoid using deeply nested object references or making calls to the same function several times. You could use a computed, without a parameter, if you want caching.
Hmmm...no you can't use a computed here. It's in a loop. That was the whole point here.
Indeed, you’re right. A normal computed wouldn’t have access to the v-for parameter here and a parametrized computed wouldn’t be cached.
If you need caching then you will need a different technique.
You could declare a data variable and then assign the v-for iterator to it, like I have shown in my example. In this way, a computed would have access to that data variable with the iterator’s value. Would this work for you?
This may work in JS but TypeScript can't make heads or tails of this. The referenced function will be marked as un-used, causing a TS error, and the unknown variable will also be marked as unknown. You can define the var in the
<script>
block, but it will still leave the previous error.A better approach may be to use a WeakMap in the getUserData function. Like:
Repeated calls to
getUserData
within the loop, then, will instantly return the item, and once we're out of that iteration of the loop, the cached result will be garbage collected.I didn't put in TypeScript annotations but TS handles this approach much better as well.