DEV Community

Discussion on: (An Approach to) Vue.js Template Variables

Collapse
 
pbastowski profile image
pbastowski

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:

<li v-for="id in users" :key="id" :set="item = getUserData(id)">
    <img :src="item.avatar" /><br />
    🏷️ {{ item.name }}<br />
    🔗 {{ item.homepage }}
</li>
Enter fullscreen mode Exit fullscreen mode

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

Here is a fork of Florian's code (thank's Florian) showing the pattern at work.
codesandbox.io/s/6nwyw3zzwz

Collapse
 
loilo profile image
Florian Reuschel

This is definitely a clever hack. A little bit dirty, but absolutely clever. 😁

Collapse
 
pbastowski profile image
pbastowski

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 ;)

Thread Thread
 
loilo profile image
Florian Reuschel

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.

Thread Thread
 
pbastowski profile image
pbastowski

I hear you.

Collapse
 
blocka profile image
Avi Block

How does this solve the fact that the call is not memoized?

Collapse
 
pbastowski profile image
pbastowski

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.

Thread Thread
 
blocka profile image
Avi Block

Hmmm...no you can't use a computed here. It's in a loop. That was the whole point here.

Thread Thread
 
pbastowski profile image
pbastowski • Edited

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?

Collapse
 
matthewdean profile image
Matthew Dean

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.

Collapse
 
matthewdean profile image
Matthew Dean

A better approach may be to use a WeakMap in the getUserData function. Like:

const itemFromId = new WeakMap()
function getUserData(id) {
  let item = itemFromId.get(id)
  if (item) return item
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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.