Skip to content

Fixed v-for/v-if priority and explanations #540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Sep 25, 2020
2 changes: 1 addition & 1 deletion src/api/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

This directive triggers transitions when its condition changes.

When used together with `v-if`, `v-for` has a higher priority than v-if. See the [list rendering guide](../guide/list.html#v-for-with-v-if) for details.
When used together, `v-if` has a higher priority than `v-for`. We don't recommend using these two directives together on one element — see the [list rendering guide](../guide/list.html#v-for-with-v-if) for details.

- **See also:** [Conditional Rendering - v-if](../guide/conditional.html#v-if)

Expand Down
2 changes: 1 addition & 1 deletion src/guide/conditional.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,4 @@ Generally speaking, `v-if` has higher toggle costs while `v-show` has higher ini
Using `v-if` and `v-for` together is **not recommended**. See the [style guide](../style-guide/#avoid-v-if-with-v-for-essential) for further information.
:::

When used together with `v-if`, `v-for` has a higher priority than `v-if`. See the [list rendering guide](list#v-for-with-v-if) for details.
When `v-if` and `v-for` are both used on the same element, `v-if` will be evaluated first. See the [list rendering guide](list#v-for-with-v-if) for details.
15 changes: 7 additions & 8 deletions src/guide/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,25 +268,24 @@ Similar to template `v-if`, you can also use a `<template>` tag with `v-for` to
Note that it's **not** recommended to use `v-if` and `v-for` together. Refer to [style guide](../style-guide/#avoid-v-if-with-v-for-essential) for details.
:::

When they exist on the same node, `v-for` has a higher priority than `v-if`. That means the `v-if` will be run on each iteration of the loop separately. This can be useful when you want to render nodes for only _some_ items, like below:
When they exist on the same node, `v-if` has a higher priority than `v-for`. That means the `v-if` condition will not have access to variables from the scope of the `v-for`:

```html
<!-- This will throw an error because property "todo" is not defined on instance. -->

<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
```

The above only renders the todos that are not complete.

If instead, your intent is to conditionally skip execution of the loop, you can place the `v-if` on a wrapper element (or [`<template>`](conditional#conditional-groups-with-v-if-on-lt-template-gt)). For example:
This can be fixed by moving `v-for` to a wrapping `<template>` tag:

```html
<ul v-if="todos.length">
<li v-for="todo in todos">
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
</template>
```

## `v-for` with a Component
Expand Down
79 changes: 17 additions & 62 deletions src/style-guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ There are two common cases where this can be tempting:
- To avoid rendering a list if it should be hidden (e.g. `v-for="user in users" v-if="shouldShowUsers"`). In these cases, move the `v-if` to a container element (e.g. `ul`, `ol`).

::: details Detailed Explanation
When Vue processes directives, `v-for` has a higher priority than `v-if`, so that this template:
When Vue processes directives, `v-if` has a higher priority than `v-for`, so that this template:

``` html
<ul>
Expand All @@ -210,19 +210,9 @@ When Vue processes directives, `v-for` has a higher priority than `v-if`, so tha
</ul>
```

Will be evaluated similar to:
Will throw an error, because the `v-if` directive will be evaluated first and the iteration variable `user` does not exist at this moment.

``` js
this.users.map(user => {
if (user.isActive) {
return user.name
}
})
```

So even if we only render elements for a small fraction of users, we have to iterate over the entire list every time we re-render, whether or not the set of active users has changed.

By iterating over a computed property instead, like this:
This could be fixed by iterating over a computed property instead, like this:

``` js
computed: {
Expand All @@ -243,40 +233,18 @@ computed: {
</ul>
```

We get the following benefits:
Alternatively, we can use a `<template>` tag with `v-for` to wrap the `<li>` element:

- The filtered list will _only_ be re-evaluated if there are relevant changes to the `users` array, making filtering much more efficient.
- Using `v-for="user in activeUsers"`, we _only_ iterate over active users during render, making rendering much more efficient.
- Logic is now decoupled from the presentation layer, making maintenance (change/extension of logic) much easier.

We get similar benefits from updating:

``` html
```html
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>
</ul>
```

to:

``` html
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
```

By moving the `v-if` to a container element, we're no longer checking `shouldShowUsers` for _every_ user in the list. Instead, we check it once and don't even evaluate the `v-for` if `shouldShowUsers` is false.
:::

<div class="style-example style-example-bad">
Expand All @@ -293,18 +261,6 @@ By moving the `v-if` to a container element, we're no longer checking `shouldSho
</li>
</ul>
```

``` html
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
```
</div>

<div class="style-example style-example-good">
Expand All @@ -321,14 +277,13 @@ By moving the `v-if` to a container element, we're no longer checking `shouldSho
</ul>
```

``` html
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
```html
<ul>
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>
</ul>
```
</div>
Expand Down