[Vue.js] 4. 制御のディレクティブ

  1. 4.1 条件分岐のディレクティブ
    1. 4.1.1 条件分岐ディレクティブの基本 v-if
      1. 条件に合致した場合にタグがレンダリングされる
      2. 複雑な式では算出プロパティを活用する
    2. 4.1.2 条件分岐ディレクティブをフルセットで利用した場合
      1. v-else-if と v-else ディレクティブ
      2. 条件分岐ディレクティブタグは連続して配置する
    3. 4.1.3 ディレクティブ記述のためのタグ template
    4. 4.1.4 v-if と似て非なる v-show
  2. 4.2 ループのディレクティブ
    1. 4.2.1 ループのディレクティブである v-for
      1. v-for の基本構文
      2. ループ対象タグに必須の v-bind:key
      3. 配列ループでインデックスを表示するには
        1. インデックス値は v-bind:key には利用できない
    2. 4.2.2 連想配列のループ
      1. 連想配列のループでのエイリアス記述
      2. key の値の工夫
      3. 連想配列ループでのインデックス利用
    3. 4.2.3 Map のループ
      1. Map を利用するには new して set() する
      2. Map のループのエイリアス記述
    4. 4.2.4 オブジェクトのループ(ループパターンの紹介その1)
      1. オブジェクトのループは連想配列と同じ
      2. 複数のタグを繰り返すには template を使う
    5. 4.2.5 オブジェクトの配列のループ(ループパターンの紹介その2)
    6. 4.2.6 Map とオブジェクトの組み合わせのループ(ループパターンの紹介その3)
    7. 4.2.7 カウンタ変数を利用したループ(ループパターンの紹介その4)
      1. カウンタ変数ループで開始の値を変更するテクニック
  3. 4.3 リスト操作
    1. 4.3.1 ループ対象データの絞り込み
    2. 4.3.2 配列のデータ操作
    3. 4.3.3 Map データ操作
    4. 4.3.2 オブジェクト内のデータ変更
    5. 4.3.5 リストデータ内のオブジェクトの変更
      1. 型アサーション

4.1 条件分岐のディレクティブ

通常のプログラミングの条件分岐と同じく if、else if、else をディレクティブにしたもの。

4.1.1 条件分岐ディレクティブの基本 v-if

<script setup lang="ts">
import { computed, ref } from 'vue'

const number = ref(80)

const showOrNot = computed((): boolean => {
  let showOrNot = false
  const rand = Math.round(Math.random() * 100)  // 乱数発生
  if (rand >= 50) {  // 乱数が50以上ならtrue
    showOrNot = true
  }
  return showOrNot
})
</script>

<template>
  <p v-if="number >= 50">条件に合致したので表示①</p>
  <p v-if="Math.round(Math.random() * 100) >= 50">条件に合致したので表示②</p>
  <p v-if="showOrNot">条件に合致したので表示③</p>
</template>

条件に合致した場合にタグがレンダリングされる

テンプレート1行目:テンプレート変数 number は常に80なのでレンダリングされ表示される。

複雑な式では算出プロパティを活用する

テンプレート2行目:v-ifの条件部分には式を記述できる。乱数で発生した値が50以上の場合レンダリングされ表示される。

しかし、可読性という観点からはテンプレートに式を直接記述するのは良いコードとは言えず、Vueのスタイルガイドに反する。

複雑な条件式は、テンプレート3行目のように算出プロパティとして記述する。

4.1.2 条件分岐ディレクティブをフルセットで利用した場合

<script setup lang="ts">
import { computed } from 'vue'

const randomNumber = computed((): number => {
  return Math.round(Math.random() * 100)
})
</script>

<template>
  <p>
    点数は{{ randomNumber }}点で
    <span v-if="randomNumber >= 80">優です</span>
    <span v-else-if="randomNumber >= 70">良です</span>
    <span v-else-if="randomNumber >= 60">可です</span>
    <span v-else>不可です</span>
  </p>
</template>

v-else-if と v-else ディレクティブ

JavaScriptの条件分岐構文と同じだが else if に反しては v-else-if と else と if の間にハイフンを入れる。
else はそのまま v-else であり属性値としての条件指定は不要。

v-if="条件"
 :
v-else-if="条件"
 :
v-else

条件分岐ディレクティブタグは連続して配置する

一連の条件分岐ディレクティブを記述したタグとタグの間には別のタグを入れられない。
次のような記述はできない。

    <span v-if="randomNumber >= 80">優です</span>
    <span class="colorRed">すばらしい!</span>
    <span v-else-if="randomNumber >= 70">良です</span>

4.1.3 ディレクティブ記述のためのタグ template

条件分岐ディレクティブでは条件に合致した場合に、ディレクティブが記述されたタグとその配下のタグがレンダリングされる。
複数のタグをレンダリングするかどうか判定する場合は、それらをまとめるタグを配置し、そのタグに条件分岐ディレクティブを記述する必要がある。

<div v-if="showOrNot">
  <img alt="vue logo" src="./assets/logo.svg">
  <p>ロゴを表示</p>
</div>

上記は showOrNotが True の時に、複数タグ(imgタグとpタグ)をレンダリングするコード例。
複数タグを<div>タグでまとめて、<div>タグに条件分岐ディレクティブを記述している。

条件に合致すると<div>自体もレンダリングされるが、この<div>タグはディレクティブのために記述しているがタグとしては意味がない。

無意味なタグが増え、タグ階層が無駄に深くなるのを避けるために template タグが用意されている。
template タグは条件に合致してこの内部のコードがレンダリングされる際にはタグとしてレンダリングされない。

<template v-if="showOrNot">
  <img alt="vue logo" src="./assets/logo.svg">
  <p>ロゴを表示</p>
</template>

4.1.4 v-if と似て非なる v-show

<script setup lang="ts">
import { computed } from 'vue'

const showOrNot = computed((): boolean => {
  let showOrNot = false
  const rand = Math.round(Math.random() * 100)
  if (rand >= 50) {
    showOrNot = true
  }
  return showOrNot
})
</script>

<template>
  <section>
    v-ifを利用
    <p v-if="showOrNot">条件に合致したので表示</p>
  </section>
  <section>
    v-showを利用
    <p v-show="showOrNot">条件に合致したので表示</p>
  </section>
</template>
<section> 
  v-ifを利用
  <p>条件に合致したので表示</p>
</section>

<section>
  v-showを利用
  <p>条件に合致したので表示</p>
</section>
<section>
  v-ifを利用
  <!--v-if-->
</section>

<section>
  v-showを利用
  <p style="display: none;">条件に合致したので表示</p>
</section>

条件が合致した場合 v-if と v-show は同じ結果になる。
条件が合致しなかった場合、v-if はレンダリングされず v-show は display: none になる。

v-show は常にレンダリングされるため、表示/非表示の切り替えコストが低く、頻繁に切り替えるような処置に向く
v-if は条件に合致しなかった場合にレンダリングコストが全くかからないので表示/非表示があらかじめ決まっていて切り替えが不要な場合は v-if の方が適している

注意: template タグはレンダリングされる際に消えるので、v-show は使用できない。

4.2 ループのディレクティブ

4.2.1 ループのディレクティブである v-for

<script setup lang="ts">
import { ref } from 'vue'

const cocktailListInit: string[] = ['ホワイトレディ', 'ブルーハワイ', 'ニューヨーク']
const cocktailList = ref(cocktailListInit)
</script>

<template>
  <ul>
    <li v-for="cocktailName in cocktailList" v-bind:key="cocktailName">{{ cocktailName }}</li>
  </ul>
  <ul>
    <li v-for="(cocktailName, index) in cocktailList" v-bind:key="cocktailName">
      {{ cocktailName }}(インデックス{{ index }})
    </li>
  </ul>
</template>

ループディレクティブとして用意されているのは v-for のみ。

v-for の基本構文

v-for="各要素を格納する変数 in ループ対象"

各要素を格納する変数をVueではエイリアスとよんでいる。

ループ対象タグに必須の v-bind:key

v-bind:key は、ループ処理によって生成された各要素を Vue 本体から識別するためキー文字列をバインドする記述。
この属性はコンポーネントのテンプレート部で v-for ディレクティブを使用するときは一緒に記述することが推奨されている。

属性値として指定するのは、要素(タグ)を識別するという目的上、ループ処理後一意となる文字列が的している。

配列ループでインデックスを表示するには

v-for="(各要素を格納する変数, インデックス値を格納する変数) in ループ対象"
インデックス値は v-bind:key には利用できない

インデックス値は一意になるがそれが必ずしも要素を識別することにはならない。
例えば配列の間に別の値を挿入すると各要素に対応するインデックスの値がかわってしまい識別の役割が果たせない。
真にそのデータを識別可能な値を用意してそれを v-bind:key に指定する

4.2.2 連想配列のループ

オブジェクトを連想配列として利用した場合のキーと値のペアをループで処理する場合。

<script setup lang="ts">
import { ref } from 'vue'

const cocktailListInit: { [key: number]: string } = {
  2345: 'ホワイトレディ',
  4412: 'ブルーハワイ',
  6792: 'ニューヨーク',
}

const cocktailList = ref(cocktailListInit)
</script>

<template>
  <ul>
    <li v-for="(cocktailName, id) in cocktailList" v-bind:key="'cocktailList' + id">
      IDが{{ id }}のかくてるは{{ cocktailName }}
    </li>
  </ul>
  <ul>
    <li v-for="(cocktailName, id, index) in cocktailList" v-bind:key="'cocktailListWithIdx' + id">
      {{ index + 1 }}: IDが{{ id }}のカクテルは{{ cocktailName }}
    </li>
  </ul>
</template>
連想配列の型指定

const cocktailListInit: { [key: number]: string }
TypeScriptでオブジェクトリテラルを連想配列として利用する際にキーと値のデータ型を指定するための記述。
[key:number]部分をインデックスシグネチャという。

連想配列のループでのエイリアス記述

v-for="(各要素の値を格納する変数, 各要素のキーを格納する変数) in ループ対象"

key の値の工夫

連想配列のキーは各データを区別可能であるため v-bind:key にもってこいである。
通常は次のように記述すれば十分である。

v-bind:key="id"

しかし、同じデータを同一コンポーネント内で複数回ループ表示される場合は、v-bind:key が一意になるようにIDの前に文字列を付与するなど工夫する必要がある。
サンプルコードではループごとに、IDの前に「cocktailList」と「cocktailListWithIdx」を付けている。

連想配列ループでのインデックス利用

連想配列をループする際は、オブジェクトのプロパティ名とプロパティ値(キーと値)以外にインデックスも利用できる。

v-for="(各要素の値を格納する変数, 各要素のキーを格納する変数, インデックス値を格納する変数) in ループ対象"

v-for ディレクティブでエイリアスとして () 内に記述できる変数の数はこの構文の3個が最大である。
エイリアスに記述した変数はマスタッシュ構文で利用できる。

4.2.3 Map のループ

ES2015以降のJavaScriptには Map という連想配列のためのオブジェクトが導入されている。

<script setup lang="ts">
import { ref } from 'vue'

const cocktailListInit = new Map<number, string>()
cocktailListInit.set(2345, 'ホワイトレディ')
cocktailListInit.set(4412, 'ブルーハワイ')
cocktailListInit.set(6792, 'ニューヨーク')
const cocktailList = ref(cocktailListInit)
</script>

<template>
  <ul>
    <li v-for="[id, cocktailName] in cocktailList" v-bind:key="id">
      IDが{{ id }}のカクテルは{{ cocktailName }}
    </li>
  </ul>
</template>

Map を利用するには new して set() する

const cocktailListInit = new Map<number, string>()
cocktailListInit.set(2345, 'ホワイトレディ')
const cocktailList = ref(cocktailListInit)

new によって Map オブジェクトを生成する。
Map を new する際 TypeScript では <> 内にキーと値のデータ型を指定する必要がある。
カンマの前がキー、後ろがデータ型である。
この <> という記述をジェネリスクという。

データ登録では set() メソッドを利用し、第1引数にキーのデータ、第2引数に値のデータを渡す。
この Map オブジェクトを ref() 関数に渡しリアクティブなテンプレート変数としている。

Map のループのエイリアス記述

v-for="[各要素のキーを格納する変数, 各要素の値を格納する変数] in ループ対象"

連想配列と違い、エイリアス部分を丸かっこ () でなくブラケット [] で囲む
括弧内の変数の順序も丸かっことは逆で、左側にキー、右側に値が格納される。

4.2.4 オブジェクトのループ(ループパターンの紹介その1)

連想配列(インデックスシグネチャ)を利用していない通常のオブジェクトのループ

<script setup lang="ts">
import { ref } from 'vue'

// ひとつのカクテルを表すオブジェクトリテラル
const whiteLadyInit: {
  id: number
  name: string
  price: number
  recipe: string
} = {
  id: 2345,
  name: 'ホワイトレディ',
  price: 1200,
  recipe: 'ジン30ml+コワントロー15ml+レモン果汁+15ml',
}

// オブジェクトリテラルをテンプレート変数へ
const whiteLady = ref(whiteLadyInit)
</script>

<template>
  <dl>
    <template v-for="(value, key) in whiteLady" v-bind:key="key">
      <dt>{{ key }}</dt>
      <dd>{{ value }}</dd>
    </template>
  </dl>
</template>

オブジェクトのループは連想配列と同じ

TypeScript/JavaScript の連想配列は、そもそもオブジェクトを連想配列として利用しているため、その実態はオブジェクトと何ら変わりがないためループ構文も同じになる。

複数のタグを繰り返すには template を使う

リスト形式での表示に定義リスト(dlタグ)を使う場合、dlタグ内は通常 dtタグと ddタグのペアの繰り返しとなるため、ループ処理のためには dtタグと ddタグをまとめておくタグが必要であり、そのタグに v-for ディレクティブを記述することになる。
このような場合 template タグを利用し、その属性として v-for ディレクティブを指定する。

4.2.5 オブジェクトの配列のループ(ループパターンの紹介その2)

配列の要素がオブジェクトであっても問題なくループできる。

<script setup lang="ts">
import { ref } from 'vue'

// 配列の要素がオブジェクト
const cocktailDataListInit: Cocktail[] = [
  { id: 2345, name: 'ホワイトレディ', price: 1200 },
  { id: 4412, name: 'ブルーハワイ', price: 1500 },
  { id: 6792, name: 'ニューヨーク', price: 1100 },
  { id: 8429, name: 'マティーニ', price: 1500 },
]
const cocktailDataList = ref(cocktailDataListInit)

interface Cocktail {
  id: number
  name: string
  price: number
}
</script>

<template>
  <ul>
    <li v-for="cocktailItem in cocktailDataList" v-bind:key="cocktailItem.id">
      {{ cocktailItem.name }}の値段は{{ cocktailItem.price }}円
    </li>
  </ul>
</template>

各要素を格納する変数(cocktailItem)にはオブジェクトが入る。
各プロパティは「.」(ドット)アクセスで取り出せる。

インターフェース

オブジェクト型リテラルではオブジェクト変数を宣言する際にその型を直接記述していた。

const whiteLadyInit: {
  id: number
  name: string
  price: number
  recipe: string
} = {
  id: 2345,
  name: 'ホワイトレディ',
  price: 1200,
  recipe: 'ジン30ml+コワントロー15ml+レモン果汁+15ml',
}

オブジェクトの型定義を再利用したい場合は TypeScript ではインターフェースとして型定義を別に定義することができる。

interface Cocktail {
  id: number
  name: string
  price: number
  recipe: string
}

この定義を利用した Cocktail 型のオブジェクトはプロパティを過不足なく定義されたデータ型の値で作成する必要がある

4.2.6 Map とオブジェクトの組み合わせのループ(ループパターンの紹介その3)

Map の値としてオブジェクトを持つ場合のループ方法。

<script setup lang="ts">
import { ref } from 'vue'

// オブジェクトを要素として持つMap
// キーはナンバー型、値はCocktail型
const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(2345, { id: 2345, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(4412, { id: 4412, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(6792, { id: 6792, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(8429, { id: 8429, name: 'マティーニ', price: 1500 })

const cocktailDataList = ref(cocktailDataListInit)

interface Cocktail {
  id: number
  name: string
  price: number
}
</script>

<template>
  <ul>
    <li v-for="[id, cocktailItem] in cocktailDataList" v-bind:key="id">
      {{ cocktailItem.name }}の値段は{{ cocktailItem.price }}円
    </li>
  </ul>
</template>

Map を利用するので v-for ディレクティブのエイリアスは [] となる。
キー部分の変数名を id としていて v-bind:keyには id を利用している。

配列の要素をオブジェクトにするより、Mapを利用するとキーとして id 値を使用できるので管理しやすくなる。
Map に対して get() メソッドでキーを指定するとその値をすぐに取り出せる。id「4412」のデータが欲しい場合 Map であれば「cocktailDataList.get(4412)」で済むが配列の場合はもっと複雑なコードが必要になる。

4.2.7 カウンタ変数を利用したループ(ループパターンの紹介その4)

<script setup lang="ts">
</script>
<template>
  <ul>
    <li v-for="r in 5" v-bind:key="r">半径{{ r }}の円の円周:{{ 2 * r * 3.14 }}</li>
  </ul>
</template>
v-for="カウンタ変数 in 終端の値"

in の右側には終端の値のみ記述する。
開始時の値は 1 で固定されている。

カウンタ変数ループで開始の値を変更するテクニック

<select>
  <option v-for="year in 60" v-bind:key="year" v-bind:value="year + 1965">
    {{year + 1965}}年
  </option>
</select>

カウンタ変数を利用する段階で足し算処理を行う。

4.3 リスト操作

v-forディレクティブではループ対象としてリアクティブなテンプレート変数を利用していた。
これによって、リストを操作したときにそれに応じて表示されるリストの内容も自動で変化する。
このようなリストの操作に応じて表示が自動で変化する様子を紹介。

4.3.1 ループ対象データの絞り込み

オブジェクトを要素として持つ配列の中からデータを絞り込む場合

<li
  v-if="・・・"
  v-for="coktailItem in cocktailDataList"
  ・・・>
</li>

fv-for ディレクティブが記述された li タグ内いに v-if ディレクティブを記述する方法。
なんとかなりそうだがこの方法は Vue のスタイルガイドの Aルールに反する。

代わりとなるのは算出プロパティを使う方法である。

<script setup lang="ts">
import { ref, computed } from 'vue'

const cocktailDataListInit: Cocktail[] = [
  { id: 2345, name: 'ホワイトレディ', price: 1200 },
  { id: 4412, name: 'ブルーハワイ', price: 1500 },
  { id: 6792, name: 'ニューヨーク', price: 1100 },
  { id: 8429, name: 'マティーニ', price: 1500 },
]
const cocktailDataList = ref(cocktailDataListInit)

// データを絞り込む算出プロパティ
const cocktail1500 = computed((): Cocktail[] => {
  // 配列のfilter()メソッドを使って新たな配列を生成
  const newList = cocktailDataList.value.filter(
    // filter()メソッドの絞り込み条件関数
    // 引数は配列の各要素であるcocktailオブジェクト
    (cocktailItem: Cocktail): boolean => {
      // 値段が1500かどうかの結果を戻り値とする
      return cocktailItem.price == 1500
    },
  )
  return newList
})

interface Cocktail {
  id: number
  name: string
  price: number
}
</script>

<template>
  <section>
    全てのカクテルリスト
    <ul>
      <li v-for="cocktailItem in cocktailDataList" v-bind:key="cocktailItem.id">
        {{ cocktailItem.name }}の値段は{{ cocktailItem.price }}円
      </li>
    </ul>
  </section>
  <section>
    値段が1500円のカクテルリスト
    <ul>
      <li v-for="cocktailItem in cocktail1500" v-bind:key="'coctail1500' + cocktailItem.id">
        {{ cocktailItem.name }}の値段は{{ cocktailItem.price }}円
      </li>
    </ul>
  </section>
</template>

算出プロパティの算出関数内でリアクティブなテンプレート変数である cocktailDataList 内に格納された配列(cocktailDataLList.value)に対して配列オブジェクトのメソッドである filter() を利用して要素を絞り込み、新たな配列を newList として返しているので、絞り込まれた配列が算出プロパティとなる。

算出プロパティを定義してそれをテンプレート変数にしておけば v-for ディレクティブ側はこの算出プロパティのテンプレート変数をループ対象とするだけてよい。
ループ対象をソートして表示したい場合も同様である。

配列の filter() メソッド

配列オブジェクトの filter() メソッドは JavaScript/TypeScript の標準メソッドであり配列内の条件に合致した要素で構成された新たな配列を生成する。
このメソッドの引数には、条件に合致するかどうか判別する関数を渡す。

const cocktail1500 = computed((): Cocktail[] => {  // Coctail型の配列を返す
  const newList = cocktailDataList.value.filter(   // filter()
    (cocktailItem: Cocktail): boolean => {         // 条件判別関数(アロー式)
      return cocktailItem.price == 1500            // 要素の条件評価。true/false値を返す
    },
  )
  return newList
})

4.3.2 配列のデータ操作

元となる配列に対して、要素の追加や削除などを行った場合の表示の変化。

<script setup lang="ts">
import { ref } from 'vue'

const cocktailListInit: string[] = ['ホワイトレディ', 'ブルーハワイ', 'ニューヨーク']
const cocktailList = ref(cocktailListInit)

const changeCocktailList = (): void => {
  cocktailList.value = ['バラライカ', 'XYZ', 'マンハッタン']
}

const addCocktailList = (): void => {
  cocktailList.value.push('ブルームーン')
}

const deleteFromCocktailList = (): void => {
  cocktailList.value.pop()
}
</script>

<template>
  <ul>
    <li v-for="(cocktailName, index) in cocktailList" v-bind:key="cocktailName">
      {{ cocktailName }}(インデックス{{ index }})
    </li>
  </ul>
  <p>
    CocktailListを
    <button v-on:click="changeCocktailList">変更</button>
  </p>
  <p>
    CocktailListの末尾に「ブルームーン」を
    <button v-on:click="addCocktailList">追加</button>
  </p>
  <p>
    CocktailListから末尾の要素を
    <button v-on:click="deleteFromCocktailList">削除</button>
  </p>
</template>
  • changeCocktailList()
    配列そのものをごっそり入れ替えている
  • addCocktailList()
    配列の末尾に要素を追加
  • deleteFromCocktailList()
    配列の末尾要素を削除

リアクティブシステムのおかげで単に配列を操作するだけで表示が変わる。

push() や pop() は JavaScript/TypeScript 標準の配列操作メソッドだが、リアクティブシステムと連動するように Vue 内ではラップされた状態で用意されている。

メソッド内容
push(element)element を末尾に追加
pop()末尾の要素を削除
shift()先頭の要素を削除
unshift(element)elemnt を先頭に追加
splice(start[, count[, e1[, e2, …]]])start 番目から count 個の要素を e1, e2, …, で置き換え
sort()ソート
reverse()順番反転

上記以外のメソッドを利用する場合は、新たな配列を生成してごっそり入れ替えた方が良い。

4.3.3 Map データ操作

元となるMapに対して、要素の追加や削除などを行った場合の表示の変化。

<script setup lang="ts">
import { ref } from 'vue'

const cocktailListInit = new Map<number, string>()
cocktailListInit.set(2345, 'ホワイトレディ')
cocktailListInit.set(4412, 'ブルーハワイ')
cocktailListInit.set(6792, 'ニューヨーク')
const cocktailList = ref(cocktailListInit)

const changeCocktailList = (): void => {
  cocktailList.value.clear()
  cocktailList.value.set(3416, 'バラライカ')
  cocktailList.value.set(5517, 'XYZ')
  cocktailList.value.set(7415, 'マンハッタン')
}

const addCocktailList = (): void => {
  cocktailList.value.set(8894, 'ブルームーン')
}

const deleteFromCocktailList = (): void => {
  cocktailList.value.delete(5517)
}
</script>

<template>
  <ul>
    <li v-for="[id, cocktailName] in cocktailList" v-bind:key="id">
      IDが{{ id }}のカクテルは{{ cocktailName }}
    </li>
  </ul>
  <p>
    CocktailListを
    <button v-on:click="changeCocktailList">変更</button>
  </p>
  <p>
    CocktailListに「ブルームーン」を
    <button v-on:click="addCocktailList">追加</button>
  </p>
  <p>
    CocktailListから5517のXYZを
    <button v-on:click="deleteFromCocktailList">削除</button>
  </p>
</template>
  • changeCoktailList()
    Map の要素をごっそり入れ替えている。
    Mapには全ての要素を削除するメソッド clsar()があるのでこれを利用している。
  • addCocktailList()
    Map の末尾に要素を追加
  • deleteFromCocktailList()
    Map から指定の要素を削除
    Map の delete() メソッドには引数として削除対象のキーを指定する。これによって先頭や末尾でない途中の要素をピンポイントで削除することも可能で、これはMapの大いなるメリットである。

4.3.2 オブジェクト内のデータ変更

リアクティブシステムを活用したリスト表示の動的変更は、オブジェクト内のデータ変更についても有効である。

<script setup lang="ts">
import { ref } from 'vue'

const whiteLadyInit: {
  id: number
  name: string
  price: number
  recipe: string
} = {
  id: 2345,
  name: 'ホワイトレディ',
  price: 1200,
  recipe: 'ジン30ml+コワントロー15ml+レモン果汁+15ml',
}
const whiteLady = ref(whiteLadyInit)

const changeWhiteLadyPrice = (): void => {
  whiteLady.value.price = 1500
}
</script>

<template>
  <dl>
    <template v-for="(value, key) in whiteLady" v-bind:key="key">
      <dt>{{ key }}</dt>
      <dd>{{ value }}</dd>
    </template>
  </dl>
  <p>
    価格を1500円に
    <button v-on:click="changeWhiteLadyPrice">変更</button>
  </p>
</template>

テンプレート変数内のオブジェクトの値を変更するだけでリアクティブシステムが働き表示内容も即座に変わる。

4.3.5 リストデータ内のオブジェクトの変更

オブジェクトを要素とする Map の算出プロパティによるリストの絞り込みを行うサンプル

<script setup lang="ts">
import { ref, computed } from 'vue'

const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(2345, { id: 2345, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(4412, { id: 4412, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(6792, { id: 6792, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(8429, { id: 8429, name: 'マティーニ', price: 1500 })
const cocktailDataList = ref(cocktailDataListInit)

const cocktail1500 = computed(
  // 算出関数。戻り値の型はMap
  (): Map<number, Cocktail> => {
    // 絞り込んだ要素を格納する新しいMapを用意
    const newList = new Map<number, Cocktail>()

    // cocktailDataList内のMapをループ処理
    cocktailDataList.value.forEach(
      // ループの各処理内容を表す巻子
      (value: Cocktail, key: number): void => {
        // カクテルの価格が1500円ならnewListに登録
        if (value.price == 1500) {
          newList.set(key, value)
        }
      },
    )

    // 絞り込んだ新しいMapを戻り値とする
    return newList
  },
)

const changreWhileLadyPriceInList = (): void => {
  const whileLady = cocktailDataList.value.get(2345) as Cocktail
  whileLady.price = 1500
}

interface Cocktail {
  id: number
  name: string
  price: number
}
</script>

<template>
  <section>
    <ul>
      <li v-for="[id, cocktailItem] in cocktailDataList" v-bind:key="id">
        {{ cocktailItem.name }}の値段は{{ cocktailItem.price }}円
      </li>
    </ul>
  </section>
  <section>
    値段が1500円のカクテルリスト
    <ul>
      <li v-for="[id, cocktailItem] in cocktail1500" v-bind:key="'cocktail1500' + id">
        {{ cocktailItem.name }}の値段は{{ cocktailItem.price }}円
      </li>
    </ul>
  </section>
  <p>
    CocktailDataList内のホワイトレディの価格を1500円に
    <button v-on:click="changreWhileLadyPriceInList">変更</button>
  </p>
</template>
  • coctail1500()
    価格が1500円のデータを抽出。
  • changewhileLadyPriceInList()
    キーが 2345 の要素の価格を 1500 に変更。
    get()メソッドでピンポイントで変更している。

Mapなのでv-forのエイリアス部分が[]で記述されている。

算出プロパティ cocktail1500 では、Map には配列の filter() メソッドに当たるメソッドがないので地道に全要素をループ処理しながら条件に合致する要素だけを新しい Map オブジェクトに格納する処理を行っている。
Map には配列と同様に各要素をループする forEach() メソッドがあり、このメソッドを利用している。
forEach()には引数として各ループでの処理を記述した関数を渡す。
このループ処理関数の引数は、第1引数が Map の各要素の値、第2引数がキーとなっている。

型アサーション

const changreWhileLadyPriceInList = (): void => {
  const whileLady = cocktailDataList.value.get(2345) as Cocktail
  whileLady.price = 1500
}

Map の get() メソッドの戻り値は引数で受け取ったキーに該当するデータが存在しない場合を想定し、[undefined または値のデータ型] という仕様になっている。
このような複数のデータ型を組み合わせたデータ型を TypeScript ではユニオン型と呼んでおり、データの間に | を入れて記述する。

cocktailDataList.value の値のデータ型は Cocktail なので get() の戻り値のデータ型は次のように記述できる。

Cocktail|undefined

このとき戻り値を格納した whileLady も undfined の可能性がある。
undfined の可能性を残したまま price プロパティへアクセスしようとすると「オブジェクトは ‘undefined’ である可能性があります。」エラーが発生する。
このエラーを避けるために強制的に Cocktail 型に変換しているのが as を使った部分である。

このような as による型変換を型アサーションという。
ただし、Cocktailオブジェクトが確実に存在するとわかっているからこそ使用できる方法なので、存在するかどうかわからない場合は if 文などで存在をチェックしたうえで型変換を行うべきである。

コメント

タイトルとURLをコピーしました