[[📗11 Vueらしくスタイルをはめる]] < [[📒Vue.jsクエスト]] > [[📗13 リストの取り扱い]]
## Abstract
[[Vue]]の描画に関する条件について学びます。
## Lesson
[[Vue]]には条件を満たすかどうかによって、描画の可否を決定する[[属性 (HTML)|属性]]が用意されています。[[v-if]]と[[v-show]]の2つがあります。
### [[v-if]]
[[属性値]]が`true`のときに要素を描画し、`false`のときは要素を描画しません。たとえば、以下の[[HTML]]は1つ目の[[divタグ]]のみが表示されます。
```html
<template>
<div v-if="true">これは描画される</div>
<div v-if="false">これは描画されない</div>
</template>
```
また、`v-else`や`v-else-if`と組み合わせて、[[JavaScript]]における`else`や`else if`文相当の表現も可能です。
```html
<script setup lang="ts">
const x = 15;
</script>
<template>
<div v-if="x > 100">これは描画されない</div>
<div v-else-if="x > 10">これは描画される</div>
<div v-else>これは描画されない</div>
</template>
```
### [[v-show]]
[[属性値]]が`true`のときに要素を表示し、`false`のときは表示しません。たとえば、以下の[[HTML]]は1つ目の[[divタグ]]のみが表示されます。
```html
<template>
<div v-show="true">これは表示される</div>
<div v-show="false">これは表示されない</div>
</template>
```
### [[v-if]]と[[v-show]]の違い
[[v-ifとv-showの違い]]は以下の点です。
| [[属性 (HTML)\|属性]] | `true`のとき | `false`のとき |
| --------------------- | -------------------- | ------------------------------ |
| [[v-if]] | 要素を描画・表示する | 要素を描画**しない** |
| [[v-show]] | 要素を描画・表示する | 要素を描画するが表示**しない** |
[[v-if]]は要素を描画するかしないかを制御しますが、[[v-show]]は==常に描画はする代わりに==表示/非表示を制御します。
## Missions
### Misson 1
#🙂NORMAL
以下は`動物を取得`ボタンを押したら、結果を取得して表示するWebアプリを目指した途中のコードです。
```html
<script lang="ts" setup>
import { Ref, ref } from "vue";
interface Animal {
kind: string;
name: string;
}
const animal = ref(null) as Ref<Animal | null>;
const fetchAnimal = () => {
window.setTimeout(() => {
animal.value = { kind: "dog", name: "Goemon" };
}, 1000);
};
</script>
<template>
<div>
<button @click="fetchAnimal">動物を取得</button>
</div>
<div>
<!-- ローディング画像 -->
<img
src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/Attachments/mimizou-momochi.gif"
/>
</div>
<div v-show="animal">
<!-- 結果 -->
<pre>{{ JSON.stringify(animal) }}</pre>
</div>
</template>
```
以下2つの不具合を修正するよう、コードを変更してください。
- データ取得前は結果を表示**しない**
- ローディング画像は**データ取得中( `fetchAnimal` 実行中)のみ**表示する
%%
回答例
```html
<script lang="ts" setup>
import { Ref, ref } from "vue";
interface Animal {
kind: string;
name: string;
}
const animal = ref(null) as Ref<Animal | null>;
const loading = ref(false);
const fetchAnimal = () => {
loading.value = true;
window.setTimeout(() => {
animal.value = { kind: "dog", name: "Goemon" };
loading.value = false;
}, 1000);
};
</script>
<template>
<div>
<button @click="fetchAnimal">動物を取得</button>
</div>
<div v-show="loading">
<!-- ローディング画像 -->
<img
src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/Attachments/mimizou-momochi.gif"
/>
</div>
<div v-show="animal">
<!-- 結果 -->
<pre>{{ JSON.stringify(animal) }}</pre>
</div>
</template>
```
%%
### Mission 2
#🙂NORMAL
以下のコードはエラーになり画面が表示されません。**その理由を説明**し、**最低限の修正で動くコードになおしたdiffを提示して**ください。
```html
<script lang="ts" setup>
import { Ref, ref } from "vue";
interface Animal {
kind: string;
name: string;
}
const animal = ref(null) as Ref<Animal | null>;
const fetchAnimal = () => {
window.setTimeout(() => {
animal.value = { kind: "dog", name: "Goemon" };
}, 1000);
};
</script>
<template>
<div>
<button @click="fetchAnimal">動物を取得</button>
</div>
<div v-show="animal">
<!-- 結果 -->
<ul>
<li>{{ animal.kind }}</li>
<li>{{ animal.name }}</li>
</ul>
</div>
</template>
```
%%
回答例
初期状態では`animal`が`null`であり、`<template>`の中で`animal`のプロパティにアクセスできないから。常に描画される[[v-show]]ではなく、[[v-if]]にすることで問題を解決できる。
```diff
- <div v-show="animal">
+ <div v-if="animal">
```
%%
### Mission 3
#🙂NORMAL
以下のコードには[[Vue]]として非推奨の書き方のある行が1行あります。**その行を指定**し、**何が非推奨であるかを説明**してください。
なお、`State.animals`がNullableであることは問題ないとします。
```html
<script lang="ts" setup>
import { reactive } from "vue";
interface Animal {
kind: string;
name: string;
}
interface State {
animals: Animal[] | null;
}
const state = reactive<State>({
animals: null,
});
const fetchAnimals = () => {
window.setTimeout(() => {
state.animals = [
{ kind: "dog", name: "Goemon" },
{ kind: "cat", name: "Mitarashi" },
];
}, 1000);
};
</script>
<template>
<div>
<button @click="fetchAnimals">動物を取得</button>
</div>
<ul>
<li v-if="state.animals" v-for="animal in state.animals">
{{ animal.kind }}: {{ animal.name }}
</li>
</ul>
</template>
```
> [!hint]- Hint 1
> https://vuejs.org/guide/essentials/conditional.html#v-if-with-v-for
%%
回答例
\<li v-if="state.animals" v-for="animal in state.animals">
同一要素に[[v-if]]と[[v-for]]を両方指定してはいけないから。
%%
### Mission 4
#😵HARD
[[#Mission 3]]のコードについて、非推奨にならないようコードを修正してください。ただし、==コードの意味合いを変更しないでください。==
%%
回答例
```html
<script lang="ts" setup>
import { reactive } from "vue";
interface Animal {
kind: string;
name: string;
}
interface State {
animals: Animal[] | null;
}
const state = reactive<State>({
animals: null,
});
const fetchAnimals = () => {
window.setTimeout(() => {
state.animals = [
{ kind: "dog", name: "Goemon" },
{ kind: "cat", name: "Mitarashi" },
];
}, 1000);
};
</script>
<template>
<div>
<button @click="fetchAnimals">動物を取得</button>
</div>
<ul>
<template v-if="state.animals">
<li v-for="animal in state.animals">
{{ animal.kind }}: {{ animal.name }}
</li>
</template>
</ul>
</template>
```
%%
> [!hint]- Hint 1
> `<ul>`に[[v-if]]属性を追加するのは、コードの意味合いが変わってしまうのでNG。
> [!hint]- Hint 2
> https://vuejs.org/guide/essentials/conditional.html#v-if-on-template
## References
- [Conditional Rendering \| Vue\.js](https://vuejs.org/guide/essentials/conditional.html)