ChatGPT解决这个技术问题 Extra ChatGPT

VueJS How can I use computed property with v-for

How can I use computed property in lists. I am using VueJS v2.0.2.

Here's the HTML:

<div id="el">
    <p v-for="item in items">
        <span>{{fullName}}</span>
    </p>
</div>

Here's the Vue code:

var items = [
    { id:1, firstname:'John', lastname: 'Doe' },
    { id:2, firstname:'Martin', lastname: 'Bust' }
];

var vm = new Vue({
    el: '#el',
    data: { items: items },
    computed: {
        fullName: function(item) {
            return item.firstname + ' ' + item.lastname;
        },
    },
});

B
Bill Criswell

You can't create a computed property for each iteration. Ideally, each of those items would be their own component so each one can have its own fullName computed property.

What you can do, if you don't want to create a user component, is use a method instead. You can move fullName right from the computed property to methods, then you can use it like:

{{ fullName(user) }}

Also, side note, if you find yourself needing to pass an arguments to a computed you likely want a method instead.


What if you need to use fullName more than once and the fullName method is a complex function that would be really expense to do 10 times for each iteration? I think @PanJunie's solution would be much better in that situation.
I'd honestly do this much higher up in state. The computed approach below would break if you're working on sorted version of items. I was definitely going for safety here. It makes a lot more sense to me to make the data as reusable as possible much higher up.
My problem is, that array can change based on other items that get selected and deselected which triggers the reactivity of the array itself so I can't really do it at a higher level, I don't think, other than possibly creating a filter and adding in additional properties to the objects in the array at that point (which is not a terrible idea but just causes an addition looping through the array to do all that - twice is better than 10 times, I guess).
J
JJPandari

What you're missing here is that your items is an array, which holds all the items, but the computed is a single fullName, which just can't express all the fullNames in items. Try this:

var vm = new Vue({
    el: '#el',
    data: { items: items },
    computed: {
        // corrections start
        fullNames: function() {
            return this.items.map(function(item) {
                return item.firstname + ' ' + item.lastname;
            });
        }
        // corrections end
    }
});

Then in the view:

<div id="el">
    <p v-for="(item, index) in items">
        <span>{{fullNames[index]}}</span>
    </p>
</div>

The way Bill introduces surely works, but we can do it with computed props and I think it's a better design than method in iterations, especially when the app gets larger. Also, computed has a performance gain compared to method on some circumstances: http://vuejs.org/guide/computed.html#Computed-Caching-vs-Methods


Im using this for highlighting a row in a :class directive works better than a method.
This is less portable. If you wanted to go through a sorted copy of items and get the fullName this would fail. What should really be done is a "user" component, which takes a user object, that has a fullName computed property.
Why will it fail when items are sorted? @BillCriswell
If you were to do v-for="item in sortedVersionOfItems" the index would be off.
a
agm1984

I like the solution posted by PanJunjie潘俊杰. It gets at the heart of the issue by only iterating over this.items once (during the component creation phase), and then caching the result. This is of course the key benefit for utilizing a computed prop instead of a method.

computed: {
    // corrections start
    fullNames: function() {
        return this.items.map(function(item) {
            return item.firstname + ' ' + item.lastname;
        });
    }
    // corrections end
}

.

<p v-for="(item, index) in items">
    <span>{{fullNames[index]}}</span>
</p>

The only thing I don't like is that, in the DOM markup, fullNames is accessed by the list index. We could improve that by using .reduce() instead of .map() in the computed prop to create an Object with a key for each item.id in this.items.

Consider this example:

computed: {
    fullNames() {
        return this.items.reduce((acc, item) => {
            acc[item.id] = `${item.firstname} ${item.lastname}`;
            return acc;
        }, {});
    },
},

.

<p v-for="item in items" :key="`person-${item.id}`">
    <span>{{ fullNames[item.id] }}</span>
</p>

Note: the above example assumes that item.id is unique, such as from typical database output.


G
Gabor Simon

Maybe add another v-for that iterates through a one-item-long list:

<div id="el">
    <p v-for="item in items">
        <template v-for="fullName in [item.firstName + ' ' + item.lastName]">
            <span>{{fullName}}</span>
        </template>
    </p>
</div>

Not nice, but that's what you're looking for: an object around that span that has a property called fullName that contains that specific value.

And it's not just a vanity feature, because we may need to use that value at more than one place, eg.:

<span v-if="...">I am {{fullName}}</span>
<span v-else-if="...">You are {{fullName}}</span>
<span v-else>Who is {{fullName}}?</span>

My use case was that I was constructing dates in v-for loops (yes, another calendar), like:

<v-row v-for="r in 5">
    <v-col v-for="c in 7">
        <template v-for="day in [new Date(start.getTime() + 24*60*60*1000*((c-1) + 7*(r-1)))]">
            <span>
                Some rendering of a day like {{day.getYear()}} and
                {{day.getMonth()}} etc.
            </span>
        </template>
    </v-col>
</v-row>

(For brevity I omitted the :key="whatever" settings)

I admit that the nicest way would be to move that to a separate component, but if we create a new component for every two-liner like this, and use that component only at this single place, then we just pollute another namespace.

Maybe a v-let="day as new Date(...)" directive would be handy for such purpose.


T
Tariqul Islam

you have to pass argument in the function call

 <div id="el">
       <p v-for="item in items">
           <span>{{fullName(item)}}</span>
       </p>
 </div>

A code-only answer is not high quality. While this code may be useful, you can improve it by saying why it works, how it works, when it should be used, and what its limitations are. Please edit your answer to include explanation and link to relevant documentation.