0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue3・Nuxt4の前にTypeScriptの基本を整理してみる

0
Posted at

JavaScriptの基本を見直した次は、TypeScriptを整理してみます。
Vue3やNuxt4では<script setup lang="ts">typeref<型>()のような書き方が出てくるため、JavaScriptとの差分が少し気になっていました。

今回は、Vue3・Nuxt4で使うことを前提に、TypeScriptの基本、関数、クラスをまとめてみます。画像解析結果の一覧画面を作るときに使えそうな形で考えました。

まだ途中段階なので、勘違いなどあれば教えていただけるとうれしいです。

今日やったこと

TypeScriptの型、関数、クラスを確認し、Vue3・Nuxt4でどこまで優先して使いそうか整理しました。

今の理解では、TypeScriptはJavaScriptへ「この値には何が入るのか」という型情報を追加するものです。JavaScriptとして動くコードを書きつつ、画像名へ数値を入れる、検出件数へ文字列を入れる、といった間違いを開発中に見つけやすくしてくれそうです。(TypeScript公式ドキュメント) typescriptlang

image.avif > 「JavaScriptが“動かすための処理”なら、TypeScriptは“データの中身を間違えにくくするための目印”なのかも。」

Vue3では<script setup lang="ts">のように書くことで、コンポーネント内でTypeScriptを使えます。Vueのpropsは受け取るデータの型をできるだけ具体的にすることが推奨されているため、TypeScriptと相性がよさそうです。(Vue.js公式:TypeScriptとComposition API) (Vue.js公式:スタイルガイド) ja.vuejs

TypeScriptの基本

最初は、文字・数値・真偽値などの基本の型から確認しました。

// string 型は文字列を表す。
// 画像名や検索語、画面へ出すメッセージなどに使えそう。
const imageName: string = '画像001'

// number 型は数値を表す。
// ID、検出件数、スコアなどに使えそう。
const detectionCount: number = 3

// boolean 型は true または false を表す。
// 読み込み中か、詳細画面を開いているかなどに使えそう。
const isLoading: boolean = false

型は、変数名の後ろに: 型名の形で書きます。

// number 型と指定したため、数値だけを入れられる。
let score: number = 80

score = 90

// 文字列は入れられず、TypeScriptでは型エラーになる。
// score = '90'

ただし、値を見れば型が明らかな場合は、TypeScriptが型を推論してくれます。

// '画像001' から string 型と推論されるため、型は省略できる。
const imageName = '画像001'

// 3 から number 型と推論される。
const detectionCount = 3

今は、単純な値は型推論に任せ、関数の引数やAPIから受け取るデータなど、形を明確にしたいところで型を書くのがよさそうだと感じています。

配列とオブジェクト

一覧画面を作る場合は、複数の結果を持つ配列と、1件分の情報を持つオブジェクトが大切になりそうです。

// string[] は、文字列だけを入れる配列。
// 解析対象に付けたタグの一覧などに使えそう。
const tags: string[] = ['屋外', '夜間', '確認対象']

// number[] は、数値だけを入れる配列。
const scores: number[] = [80, 65, 92]

画像解析結果のように、項目が複数あるデータはtypeで形に名前を付けると読みやすそうです。

// 解析結果1件が持つ情報の形を定義する。
// type は値ではなく、「どのようなデータなら扱えるか」のルール。
type AnalysisResult = {
  // 結果を識別するためのID
  id: number

  // 表示する画像名
  imageName: string

  // 検出された件数
  detectionCount: number

  // 判定の候補を限定する。
// 想定外の文字列を入れにくくするために使う。
  status: '確認が必要' | '問題なし'
}

// AnalysisResult 型に合う解析結果を作る。
const result: AnalysisResult = {
  id: 1,
  imageName: '画像001',
  detectionCount: 3,
  status: '確認が必要'
}

// AnalysisResult[] は、解析結果の配列を表す。
const results: AnalysisResult[] = [
  result,
  {
    id: 2,
    imageName: '画像002',
    detectionCount: 0,
    status: '問題なし'
  }
]

interfaceもオブジェクトの形を定義できます。今の理解では、typeinterfaceの細かい使い分けを最初から覚えるより、まずはどちらかでデータの形を表せるようにするほうがよさそうです。

// interface でも、オブジェクトが持つべき項目と型を決められる。
interface ImageInfo {
  id: number
  fileName: string
  uploadedAt: string
}

関数と型

Vue3・Nuxt4では、クリック時の処理、検索結果の絞り込み、表示用データの作成などを関数として書くことが多そうです。

関数では、受け取る値の型返す値の型を指定できます。

// score は number 型で受け取り、string 型の判定を返す関数。
// 引数と返り値が分かるため、呼び出す側も使い方を判断しやすい。
const getEvaluation = (score: number): string => {
  if (score >= 60) {
    return 'Good'
  }

  return 'Bad'
}

// number 型の値を渡して、string 型の結果を受け取る。
const evaluation = getEvaluation(80)

console.log(evaluation)

画像解析結果の配列から、検出があるものだけを取り出す関数は次のように書けそうです。

type AnalysisResult = {
  id: number
  imageName: string
  detectionCount: number
  status: '確認が必要' | '問題なし'
}

// 引数は AnalysisResult の配列、返り値も AnalysisResult の配列。
// 入力と出力のデータ構造が同じだと、処理の意図を追いやすい。
const getDetectedResults = (
  results: AnalysisResult[]
): AnalysisResult[] => {
  // filter で、検出件数が1件以上の結果だけを新しい配列として返す。
  return results.filter((result) => {
    return result.detectionCount > 0
  })
}

この関数は、Vueのcomputedで絞り込み後の一覧を作るときや、Nuxtのcomposableへ検索処理をまとめるときにも使えそうです。

image.avif > 「関数に型を書くと、何を渡して何が返ってくるのかが見えやすい。処理を後から読み返すときにも助かりそう。」

クラスも触ってみる

クラスは、関連するデータと処理を1つにまとめる書き方です。

TypeScriptを学ぶうえでは基本として知っておきたいですが、Vue3・Nuxt4のComposition APIでは、画面の状態はref、処理は関数やcomposableとして書くことが多そうです。そのため、最初のVue3画面を作る段階では、クラスより関数とtypeを優先して覚えたいです。Vueのコンポーネントは再利用可能な単位ですが、<script setup>でクラスを定義しなければならないわけではありません。(Vue.js公式:コンポーネントの基本) vueframework

// クラスは、データ(プロパティ)と処理(メソッド)をまとめる書き方。
// ここでは解析結果1件を扱うための例として定義している。
class AnalysisResult {
  // constructor は new したときに最初に実行される。
// public を付けると、受け取った値をプロパティとして保持できる。
  constructor(
    public id: number,
    public imageName: string,
    public detectionCount: number
  ) {}

  // メソッドは、そのデータに関係する処理を書く場所。
// 検出件数をもとに判定メッセージを返す。
  getStatus(): string {
    if (this.detectionCount > 0) {
      return '確認が必要'
    }

    return '問題なし'
  }
}

// new を使って、クラスから実際のデータを作る。
const result = new AnalysisResult(1, '画像001', 3)

console.log(result.imageName)
console.log(result.getStatus())

この例では、resultが画像名・検出件数を持ち、getStatus()で自分の状態から判定を作っています。

ただ、Vue3の一覧画面では、次のようにtypeでデータの形を決め、関数で処理を分けたほうが、今の自分には読みやすそうです。

// 画面で扱うデータの形を定義する。
type AnalysisResult = {
  id: number
  imageName: string
  detectionCount: number
}

// データの外側に、判定する関数を置く。
// Vueのcomputedやcomposableへつなげやすそう。
const getStatus = (result: AnalysisResult): string => {
  return result.detectionCount > 0
    ? '確認が必要'
    : '問題なし'
}

継承、抽象クラス、privateprotectedなどは、今回は深追いしません。まずは「クラスはデータと処理をまとめられる書き方」と知り、Vue3・Nuxt4では関数をよく使う、と整理しておきます。

つまずいたところ

型は実際の値ではなく、開発時にデータの形を確認するための情報だと分かるまで少し混乱しました。

型と値は別もの

AnalysisResultは、実際の解析結果そのものではありません。「解析結果にはID、画像名、検出件数が入る」という設計図のようなものだと今は理解しています。

image.avif > 「型はデータそのものではなくて、“このデータには何が入る?”を決めるルールなんだね。」
// AnalysisResult は型であり、実行時に使うデータではない。
type AnalysisResult = {
  id: number
  imageName: string
}

// result は実際に扱う値。
// AnalysisResult の形に合っている必要がある。
const result: AnalysisResult = {
  id: 1,
  imageName: '画像001'
}

TypeScriptの型は、開発時のチェックや補完に使われ、JavaScriptへ変換された後には残りません。だからこそ、型へ複雑な処理を書くものではなく、値の形を伝えるものとして考えるとよさそうです。(TypeScript公式ドキュメント) typescript.dokyumento

nullを含む型

選択中の画像のように「まだ何も選ばれていない」状態がある場合、nullも型に含める必要がありそうです。

type AnalysisResult = {
  id: number
  imageName: string
}

// 最初は選択結果がないため null。
// 後から AnalysisResult が入る可能性があることを | で表す。
let selectedResult: AnalysisResult | null = null

selectedResult = {
  id: 1,
  imageName: '画像001'
}

最初はnullだけを入れていても、後から解析結果を入れたいならAnalysisResult | nullと書く必要がある点は、Vueのrefでも出てきそうです。

Vue3で使う型

Vue3のコンポーネントでは、<script setup lang="ts">にして、refやpropsへ型を付けるところから試したいです。

以下は、解析結果を画面へ一覧表示する小さな例です。

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

// 解析結果1件のデータ構造を定義する。
type AnalysisResult = {
  id: number
  imageName: string
  detectionCount: number
  status: '確認が必要' | '問題なし'
}

// ref の中には AnalysisResult 型の配列を持つと指定する。
// 画像名へ数値を入れるような間違いを見つけやすくなる。
const results = ref<AnalysisResult[]>([
  {
    id: 1,
    imageName: '画像001',
    detectionCount: 3,
    status: '確認が必要'
  },
  {
    id: 2,
    imageName: '画像002',
    detectionCount: 0,
    status: '問題なし'
  }
])
</script>

<template>
  <!-- 解析結果の配列を、1件ずつ一覧として表示する -->
  <ul>
    <!-- :key には各結果を識別できる id を使う -->
    <li v-for="result in results" :key="result.id">
      {{ result.imageName }}:{{ result.detectionCount }}件({{ result.status }})
    </li>
  </ul>
</template>

ref<AnalysisResult[]><AnalysisResult[]>はジェネリクスという書き方です。今は「このrefには解析結果の配列が入る」と指定するための記法、として覚えておきたいです。

親コンポーネントから解析結果を受け取る場合は、propsにも型を付けられます。

<script setup lang="ts">
type AnalysisResult = {
  id: number
  imageName: string
  detectionCount: number
  status: '確認が必要' | '問題なし'
}

// 親コンポーネントから result を受け取る。
// result は AnalysisResult の形で渡される前提にする。
const props = defineProps<{
  result: AnalysisResult
}>()
</script>

<template>
  <!-- 受け取った1件分の結果をカードとして表示する -->
  <article>
    <h2>{{ props.result.imageName }}</h2>
    <p>検出件数:{{ props.result.detectionCount }}件</p>
    <p>判定:{{ props.result.status }}</p>
  </article>
</template>

Nuxt4で使えそうなこと

Nuxt4では、Vue3と同じようにTypeScriptを使えます。まずはVueコンポーネント内でtyperefを扱えるようになり、その後にページ、composable、APIで同じ型を使う流れを試してみたいです。Nuxtは型情報を利用しやすい仕組みを備えており、アプリとサーバーで共有する型を置く構成も案内されています。(Nuxt4公式:TypeScript) nuxt

現時点では、Nuxt4でAPIを作る前に、次のようなイメージだけ持っておきます。

// shared/types/analysis.ts

// ページとAPIの両方で使う可能性がある解析結果の型。
// 実際に共有する構成は、Nuxt4でAPIを作るときに確認したい。
export type AnalysisResult = {
  id: number
  imageName: string
  detectionCount: number
  status: '確認が必要' | '問題なし'
}

今は、型をどこへ置くかより、同じデータ構造を複数箇所で書き直さずに済む可能性がある、と理解しておきます。

今の理解(メモ)

まだふわっとしていますが、TypeScriptはJavaScriptの処理を変えるものというより、データの扱い方を明確にするための仕組みに見えています。

  • TypeScriptはJavaScriptへ型情報を追加する
  • stringnumberbooleanは最初に覚えたい基本の型
  • string[]のように書くと、配列に入る値の型を指定できる
  • typeinterfaceは、解析結果のようなオブジェクトの形へ名前を付ける
  • '確認が必要' | '問題なし'のように書くと、入れられる文字列を限定できる
  • 関数では、引数と返り値へ型を付けられる
  • Vue3・Nuxt4では、クリック処理、検索、絞り込み、データ変換などで関数を使いそう
  • クラスはデータと処理をまとめる書き方だが、Vue3の画面を作る最初の段階では優先度は低め
  • Vue3では<script setup lang="ts">を使い、ref<型>()defineProps<型>()が出てきそう
  • Nuxt4では、画面とAPIで同じ型を使う場面がありそう

次にやりたいこと

TypeScriptの型を、Vue3・Nuxt4の小さな画面で使って確認したいです。

  • AnalysisResult型を作り、ref<AnalysisResult[]>で解析結果の一覧を持つ
  • v-forで解析結果をカード表示し、型を付けた配列が画面へ表示される流れを確認する
  • 関数へ引数と返り値の型を付け、検出件数で結果を絞り込む
  • definePropsを使い、親から受け取った解析結果を子コンポーネントで表示する
  • AnalysisResult | nullを使い、選択中の結果がない状態と、選択後の状態を扱う
  • Nuxt4でAPIを作る段階になったら、ページとAPIで型を共有する置き場を試したい

今日の整理

TypeScriptで最初に使いそうな書き方を、Vue3・Nuxt4との関係も含めて整理しました。

分類 書き方 役割 Vue3・Nuxt4での利用イメージ
基本型 string 文字列を表す 画像名、検索語、判定文
基本型 number 数値を表す ID、検出件数、スコア
基本型 boolean 真偽値を表す 読み込み中、詳細表示中
配列 string[] 同じ型の値を複数持つ タグ一覧、画像名一覧
Union型 AnalysisResult | null 複数の可能性を表す 選択中の結果、未選択状態
リテラル型 'A' | 'B' 候補を限定する 判定ステータス
型定義 type データ構造へ名前を付ける 解析結果1件分の形
型定義 interface オブジェクトの形を定義する props、APIデータの形
関数 (value: 型): 型 => {} 入力と出力を明確にする 検索、絞り込み、判定
クラス class データと処理をまとめる 既存コードを読むための基礎
Vue3 ref<型>() リアクティブな値へ型を付ける 解析結果の一覧、選択状態
Vue3 defineProps<型>() 親から受け取る値の型を決める 結果カードの表示
Nuxt4 共通の型 ページとAPIの形をそろえる 解析結果データの受け渡し

おわりに

今回は、Vue3・Nuxt4で使うことを前提に、TypeScriptの基本、関数、クラスを整理してみました。最初は型を書くことでコードが長く見えましたが、画像解析結果のように項目が複数あるデータでは、「何が入るべきか」が見えやすくなるのは助かりそうです。

特に、typeでデータの形を決め、関数の引数と返り値へ型を付け、Vue3のrefやpropsへつなげる流れを試してみたいです。フロント側で画像解析結果を見やすく扱う画面を作るときにも、データの扱いを間違えにくくする土台になるかもしれません。

学習中の内容なので、勘違いや理解不足もあると思います。もし気になる点があれば教えてもらえるとうれしいです。

 注意事項
 本ブログに掲載している内容は、私個人の見解であり、
 所属する組織の立場や戦略、意見を代表するものではありません。
 あくまでエンジニアとしての経験や考えを発信していますのでご了承ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?