Skip to content

Commit 7cb38f9

Browse files
Fixed v-for/v-if priority and explanations (#540)
* fix: fixed v-if v-for on styleguide * fix: fixed directives API for v-if v-for * fix: fixed v-if with v-for on list * fix: fixed conditional for v-if * fix: fixed typo * fix: fixed directives.md * fix: fixed error on list * fix: fixed grammar * fix: fixed styleguide `li` mention * fix: fixed grammar * Update src/api/directives.md Co-authored-by: Phan An <me@phanan.net> * Update src/guide/conditional.md Co-authored-by: Phan An <me@phanan.net> Co-authored-by: Phan An <me@phanan.net>
1 parent edcf1b1 commit 7cb38f9

File tree

4 files changed

+26
-72
lines changed

4 files changed

+26
-72
lines changed

src/api/directives.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262

6363
This directive triggers transitions when its condition changes.
6464

65-
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.
65+
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.
6666

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

src/guide/conditional.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,4 @@ Generally speaking, `v-if` has higher toggle costs while `v-show` has higher ini
9191
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.
9292
:::
9393

94-
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.
94+
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.

src/guide/list.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -268,25 +268,24 @@ Similar to template `v-if`, you can also use a `<template>` tag with `v-for` to
268268
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.
269269
:::
270270

271-
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:
271+
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`:
272272

273273
```html
274+
<!-- This will throw an error because property "todo" is not defined on instance. -->
275+
274276
<li v-for="todo in todos" v-if="!todo.isComplete">
275277
{{ todo }}
276278
</li>
277279
```
278280

279-
The above only renders the todos that are not complete.
280-
281-
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:
281+
This can be fixed by moving `v-for` to a wrapping `<template>` tag:
282282

283283
```html
284-
<ul v-if="todos.length">
285-
<li v-for="todo in todos">
284+
<template v-for="todo in todos">
285+
<li v-if="!todo.isComplete">
286286
{{ todo }}
287287
</li>
288-
</ul>
289-
<p v-else>No todos left!</p>
288+
</template>
290289
```
291290

292291
## `v-for` with a Component

src/style-guide/README.md

Lines changed: 17 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ There are two common cases where this can be tempting:
196196
- 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`).
197197

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

201201
``` html
202202
<ul>
@@ -210,19 +210,9 @@ When Vue processes directives, `v-for` has a higher priority than `v-if`, so tha
210210
</ul>
211211
```
212212

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

215-
``` js
216-
this.users.map(user => {
217-
if (user.isActive) {
218-
return user.name
219-
}
220-
})
221-
```
222-
223-
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.
224-
225-
By iterating over a computed property instead, like this:
215+
This could be fixed by iterating over a computed property instead, like this:
226216

227217
``` js
228218
computed: {
@@ -243,40 +233,18 @@ computed: {
243233
</ul>
244234
```
245235

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

248-
- The filtered list will _only_ be re-evaluated if there are relevant changes to the `users` array, making filtering much more efficient.
249-
- Using `v-for="user in activeUsers"`, we _only_ iterate over active users during render, making rendering much more efficient.
250-
- Logic is now decoupled from the presentation layer, making maintenance (change/extension of logic) much easier.
251-
252-
We get similar benefits from updating:
253-
254-
``` html
238+
```html
255239
<ul>
256-
<li
257-
v-for="user in users"
258-
v-if="shouldShowUsers"
259-
:key="user.id"
260-
>
261-
{{ user.name }}
262-
</li>
240+
<template v-for="user in users" :key="user.id">
241+
<li v-if="user.isActive">
242+
{{ user.name }}
243+
</li>
244+
</template>
263245
</ul>
264246
```
265247

266-
to:
267-
268-
``` html
269-
<ul v-if="shouldShowUsers">
270-
<li
271-
v-for="user in users"
272-
:key="user.id"
273-
>
274-
{{ user.name }}
275-
</li>
276-
</ul>
277-
```
278-
279-
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.
280248
:::
281249

282250
<div class="style-example style-example-bad">
@@ -293,18 +261,6 @@ By moving the `v-if` to a container element, we're no longer checking `shouldSho
293261
</li>
294262
</ul>
295263
```
296-
297-
``` html
298-
<ul>
299-
<li
300-
v-for="user in users"
301-
v-if="shouldShowUsers"
302-
:key="user.id"
303-
>
304-
{{ user.name }}
305-
</li>
306-
</ul>
307-
```
308264
</div>
309265

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

324-
``` html
325-
<ul v-if="shouldShowUsers">
326-
<li
327-
v-for="user in users"
328-
:key="user.id"
329-
>
330-
{{ user.name }}
331-
</li>
280+
```html
281+
<ul>
282+
<template v-for="user in users" :key="user.id">
283+
<li v-if="user.isActive">
284+
{{ user.name }}
285+
</li>
286+
</template>
332287
</ul>
333288
```
334289
</div>

0 commit comments

Comments
 (0)