Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Sometimes you will encounter such a situation:

<template>
  <div class="container">
    <p class="row">{{item && item.a && item.a.b && item.a.b.hello}}</p>
    <p class="row">{{item && item.a && item.a.b && item.a.b.world}}</p>
    <p class="row">{{item && item.a && item.a.b && item.a.b.goodbye}}</p>
  </div>
</template>

These code with the same logic need to be written many times.
I hope there is a way to define local variables in the template, then the above code can be abbreviated as:

<template>
  <div class="container">
    <var name="b" :value="item && item.a && item.a.b"></var>
    <p class="row">{{b && b.hello}}</p>
    <p class="row">{{b && b.world}}</p>
    <p class="row">{{b && b.goodbye}}</p>
  </div>
</template>

or

<template>
  <div class="container" v-var:b="item && item.a && item.a.b">
    <p class="row">{{b && b.hello}}</p>
    <p class="row">{{b && b.world}}</p>
    <p class="row">{{b && b.goodbye}}</p>
  </div>
</template>

Q: Why not computed?
A: The item in the above code may come from v-for, so computed is not a feasible solution.

You must be logged in to vote

Replies: 26 comments · 11 replies

Comment options

It was proposed in the past: vuejs/vue#7325
Currently you can achieve it with https://github.com/posva/vue-local-scope

You must be logged in to vote
0 replies
Comment options

Probably you already know, but if you use JSX you could do this:

export default {
    render() {
        const b = this.item && this.item.a && this.item.a.b;
        return (
            <div class="container">
                <p class="row">{b && b.hello}</p>
                <p class="row">{b && b.world}</p>
                <p class="row">{b && b.goodbye}</p>
            </div>
        )
    }
}
You must be logged in to vote
0 replies
Comment options

Q: Why not computed?
A: The item in the above code may come from v-for, so computed is not a feasible solution.

You can also create a method that accepts a parameter, so you can use it in v-for loops.

You must be logged in to vote
0 replies
Comment options

The issue with methods is that they aren't cached, so a bit less performatic.

You must be logged in to vote
0 replies
Comment options

Creating a local variable as proposed by OP would also not be cached.

I see, and a local variable could serve as an in-loop cache for method results where computed props can't be used.

You must be logged in to vote
0 replies
Comment options

Q: Why not computed?
A: The item in the above code may come from v-for, so computed is not a feasible solution.

Whenever I hear someone say that, I think "this is a perfect situation for creating a component for that item!". That would give you the ability to use simple computed getters that can be cached.

A component should always have exactly one reason to exist and one mission to execute. This parent components mission is not knowing how to render the child items.

You must be logged in to vote
0 replies
Comment options

Well, component instantiations come at a price (memory-footprint & render performance), so sometimes it's better to render i list's items in the parent for that reason. There shuold be ways to keep these situations performant, an this RFC aims to address it.

You must be logged in to vote
0 replies
Comment options

Forgive my ignorance, I'm new to the vue community.

Could the above be done with optional chaining as well? e.g.

<template>
  <div class="container">
    <p class="row">{{ item?.a?.b?.hello }}</p>
    <p class="row">{{ item?.a?.b?.world }}</p>
    <p class="row">{{ item?.a?.b?.goodbye }}</p>
  </div>
</template>

it's stage 3 and has a babel plugin

You must be logged in to vote
0 replies
Comment options

Not right now, as vue-es2015-template compiler doesn't aupport >es6 features

But that can be solved.

You must be logged in to vote
0 replies
Comment options

I was dealing with a similar concern recently in my work so wanted to add 2 cents...

(My day job is in Ember so I'll pseudo-code the concepts)

Generally I want to pass a data model to my components.. like pass a user to a component for showing the user's badge or pass a meeting to a component for showing when and where and who's attending that meeting... it's just simpler to do <my-component meeting=meeting> than to do <my-component name=meeting.name date=meeting.date whatever=meeting.whatever ...>

I've also recently come to appreciate that this allows me to flatten out deeply nested data and to add logic around data that's not directly suited to my use case. So I recently did something like this:

// Component receives `model`
name = model.model_name
date = computed(() => (model.date && date_from_format(model.date, 'YYYY-MM-DD')) || undefined)
instructions = computed(() => (model.location && model.location.meeting_instructions) || undefined)

The same can be done for lists... if I need to display a list of model.users, I originally considered passing them to a nested component as well, but sometimes that's overkill.

users = computed(() => {
  return model.users.map((user) => {
    const spouse = model.spouse || {};
    return {
      name: user.name,
      spouseName: spouse.name
    };
  });
});

Now the template can iterate users without fear of accessing a nested property that might not exist and my component js continues to uphold the contract that it's explicitly handling all the data that the template needs.

You must be logged in to vote
0 replies
Comment options

Will this make the code much less clear? In my opinion, if you have a large component, it will be difficult to understand what the variable is, where it comes from if it is not in the data of component.

Much simpler code that doesn't have any "magic" variables:

<template>
  <div class="container" v-if="item && item.a && item.a.b">
    <p class="row">{{item.a.b.hello}}</p>
    <p class="row">{{item.a.b.world}}</p>
    <p class="row">{{item.a.b.goodbye}}</p>
  </div>
</template>
You must be logged in to vote
0 replies
Comment options

Computed can also solve v-for performance problem, as items could be mapped to have additional keys for display purposes + you can use object destructuring.

Btw. most of times proper Item structurizing can solve this issue.

You must be logged in to vote
0 replies
Comment options

I think I found some kind of way to accomplish in-template variables in vue 3. Here is an example where I create the "in-template variable" inside of a v-for. Basically declare an inline array and iterate over the single item.

<div v-for='item in items'>
    <div v-for="record of [cache.find(item.otherId).value]">
        <span v-if="record">{{ record.name }}</span>
    </div>
</div>

In this case cache.find(item.otherId) returns back a ref<{ name: string } | null>

I feel like it's its kinda hacky, and I'm not really sure the performance impact.

You must be logged in to vote
0 replies
Comment options

The issue with methods is that they aren't cached, so a bit less performatic.

Which is why there are computed functions that do cache results. :-)

You must be logged in to vote
0 replies
Comment options

<div v-data="{x:'my temp variable'}">{{x}}</div>

You must be logged in to vote
0 replies
Comment options

I think I found some kind of way to accomplish in-template variables in vue 3. Here is an example where I create the "in-template variable" inside of a v-for. Basically declare an inline array and iterate over the single item.

<div v-for='item in items'>
    <div v-for="record of [cache.find(item.otherId).value]">
        <span v-if="record">{{ record.name }}</span>
    </div>
</div>

In this case cache.find(item.otherId) returns back a ref<{ name: string } | null>

I feel like it's its kinda hacky, and I'm not really sure the performance impact.

It's great how this approach can be used to cache cross-referenced (calculated) item when I have two v-for for matching correct item from big list. This way I don't need to re-evaluate fetching of correct item for each action separately, but can simply fetch item only once and refer to it.

It looks like this works correctly, even with events. I would hope to see this implement in more readable manner and without using v-for in hackish-way. Please consider implementing this as built-in-feature.

You must be logged in to vote
0 replies
Comment options

There is also a way to set multiple variables in :set directive in such way:

                        <div :set="br = {variable1: someCalculation(), variable2: anotherCalculations()}">
                            <pre>{{br}}</pre>
                        </div>

https://stackoverflow.com/a/56249209/2114398

https://dev.to/pbastowski/comment/7fc9

You must be logged in to vote
0 replies
Comment options

Svelte implements this feature and has good Typescript support.

https://svelte.dev/docs#template-syntax-const

You must be logged in to vote
0 replies
Comment options

<div v-data="{x:'my temp variable'}">{{x}}</div>

I can't find any documentation on v-data.

You must be logged in to vote
0 replies
Comment options

I can't find any documentation on v-data.

It was just a suggestion for the syntax, I believe.

You must be logged in to vote
0 replies
Comment options

I've a solution that is using a component to solve this issue.

Only tested in vue >= 3.3.0

https://www.npmjs.com/package/vue-temp-var

Example

<script setup lang="ts">
import TempVar from 'vue-temp-var'

function getRandomNumber() {
  return Math.random()
}
</script>

<template>
  <div v-for="i in 10" :key="`random-${i}`">
    <!-- cause the return value would be random, we should store it somewhere -->
    <!-- bind a prop 'define' to 'TempVar'. Because it is a prop, it would still be reactive -->    
    <!-- destruct from slot: defined, the type would be kept -->
    <TempVar 
      :define="{ randomNum: getRandomNumber() }"
      #defined="{ randomNum }"
    >
      <span>"{{ randomNum }}"</span>
      <span v-if="randomNum > 0.5"> is larger than 0.5</span>
      <span v-else> is smaller than 0.5</span>
    </TempVar>
  </div>
</template>
You must be logged in to vote
0 replies
Comment options

LocalScope.vue

<template>
	<slot v-bind="$attrs" />
</template>
<template>
	<LocalScope v-slot="{ classes }" classes="py-3 font-semibold">
		<p :class="classes">some text</p>
		<p :class="classes">some text</p>
	</LocalScope>
</template>
You must be logged in to vote
5 replies
@DevilTea
Comment options

This looks more neat! It's not necessary to use another template to consume bounded slot props!

But unfortunately, this method would not keep the type of the local scope variables.🤔

@unrevised6419
Comment options

I suppose this now can be achieved using generics component 🤔

https://vuejs.org/api/sfc-script-setup.html#generics

@DevilTea
Comment options

Yes, my solution is a component that is achieved by using generics component. I just wrapped it and distributed it to npm 😂.

Here is the component's source code

<script setup lang="ts" generic="T extends Record<any, any>">
defineProps<{ define: T }>()
</script>

<template>
	<slot v-bind="(define as T)" />
</template>
@unrevised6419
Comment options

Strange why defineProps<T>() cannot be used 🤔 it does not work

Error: [@vue/compiler-sfc] Unresolvable type reference or unsupported built-in utility type

src/Comp.vue
4  |  
5  |  <script setup lang="ts" generic="T">
6  |  defineProps<T>()
   |              ^
7  |  </script>
@unrevised6419
Comment options

I found out how to avoid that error and created a lib for this component.

https://www.npmjs.com/package/@allindevelopers/vue-local-scope

Comment options

I guess the most simple solution looks like this

<!-- local-scope.vue -->
<script setup lang="ts" generic="T extends Record<string, unknown>">
defineProps<{ set: T }>();
</script>

<template>
  <slot v-bind="set" />
</template>

And you can use it like this

<!-- my-component.vue -->
<script setup lang="ts">
import LocalScope from './local-scope.vue';
</script>

<template>
  <local-scope
    :set="{ someVar: someValue }"
    v-slot="{ someVar }"
  >
    {{ someVar }}
  </local-scope>
</template>

The thing is - it's overhead having a component lifecycle just to define a variable while in JSX you could easilly define a variable inside a function.

Maybe we could have something like v-def="{ someVar: someValue }" that transpiles to const someVar = someValue inside the render function.

Or, to make it look more like standard JS, we could have

v-def="someVar = someValue, anotherVar = anotherValue"

transpiled to

const someVar = someValue, anotherVar = anotherValue

And, with Typescript,

v-def="
  someVar: number = someValue,
  anotherVar = anotherValue as string
"

would just work.

You must be logged in to vote
0 replies
Comment options

I use a functional component and force (sort of roughly) a type onto it. That way, consumers can choose a fitting name without having to rename the variable. It even allows multiple assignments. What do you think?

See the demo. (Note: In the playground typing for v-slot does not seem to work but it works in my editor.)

<script setup>
import TempVar from './temp-var';
</script>

<template>
  <TempVar :foo="Math.random()" v-slot="{ foo }">
    <div>{{ foo }}</div>
  </TempVar>
</template>
// temp-var.ts
const TempVar = defineComponent(
  (props, ctx) => () => ctx.slots.default?.(ctx.attrs),
  { inheritAttrs: false },
) as unknown as new <T>(props: T) => {
  $props: T;
  $slots: { default?: (context: T) => unknown };
};

export default TempVar;
You must be logged in to vote
0 replies
Comment options

I created this library for myself https://www.npmjs.com/package/@allindevelopers/vue-local-scope

The code is pretty simple https://github.com/allindevelopers/vue-local-scope/blob/main/lib/local-scope.component.vue

LocalScope.vue

<script setup lang="ts" generic="T">
defineOptions({ inheritAttrs: false });
defineProps</* @vue-ignore */ T>();
defineSlots<{ default?(props: T): any }>();
</script>

<template>
	<slot v-bind="$attrs as T" />
</template>
You must be logged in to vote
6 replies
@unrevised6419
Comment options

@aa-ndrej will need to check probably something changed in latest Vue version and it needs some adjustments.
Actually my first implementation was similar to your example.

@unrevised6419
Comment options

Checked. For me this is also reported in vscode.

And it seems this "regression" started with vue-tsc@3
Downgrading to vue-tsc@2 type checking seems to work fine

Still reported because vscode version of vue-tsc is not taken from node_modules

2025-08-07.18-55-59.mov
@unrevised6419
Comment options

Reported an issue vuejs/language-tools#5592

@unrevised6419
Comment options

The issues, seems to already be fixed, and will be available in the next release.

vuejs/language-tools#5424

@aa-ndrej
Comment options

Thank you for the quick and excellent follow-up! 🙏🏼

Comment options

But why do we need workarounds for such a basic feature that should be available in vuejs by default

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Converted from issue

This discussion was converted from issue #73 on May 08, 2023 09:02.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.