Vue3でファイルアップロード&モーダルプレビュー

Vue3の<scrpt setup lang="ts">の練習用にファイルアップロード機能を実装。バリデーションは未実装です。
refreactiveでどちらを使うべきか迷いましたが、refを使った方がよさそうだったのでこちらを選択しました。

Github公開してあります。
vue3_fileupload_demo
https://github.com/neru-ne/vue3_fileupload_demo

ref()について

最初こんがらがってわからなかったのですが、
<script setup lang="ts"></script>の中では、refで定義したものに関しては、.valueでアクセスする必要があります。
<template></template>の中では、.valueなしでアクセスできました。

<script setup lang="ts">
const showModal = ref(false);

//アクセスしたい場合 .valueが必要
showModal.value = true
</script>


<template>
//templateの中では、.valueなしでアクセスできる。
 <Modal v-if="showModal" @close="showModal = false">
</Modal>
</template>

イベントの型

clickなどのイベントの型はe:Eventでいけるようです。
e.targetの場合はif (e.target instanceof HTMLInputElement)で囲むと大丈夫でした。

// 画像データ等取得
const onImageUploaded = (e: Event) => {
  if (e.target instanceof HTMLInputElement) {
    if (e.target.files) {
      const image = e.target.files[0];
      const _thisId = e.target.id;
      let _thisIndex = _thisId.split("__");
      let _thisIndexNumber = Number(_thisIndex[1]);

      createImage(image, _thisIndexNumber);
    }
  }
};

e.currentTargetを取得してgetAttributeを取得しようとしたら怒られた

e.currentTargetの時、プロパティ 'getAttribute' は型 'EventTarget' に存在しません。と怒られてしまいました。
その時はas HTMLElementを追記すると良いようです。

  const _this = e.currentTarget as HTMLElement;
  const dataFileId = _this.getAttribute("data-fileId");//怒られない

アップロードされたファイル情報を取得

//画像入れ込み
const createImage = (image: File, _thisIndexNumber: number) => {
  //FileReader.readAsDataURL()を使用してプレビュー用の画像データを取得。
  const reader = new FileReader();
  reader.readAsDataURL(image);
  reader.onload = () => {
    file.value.selectedFile[_thisIndexNumber].name = image.name;
    file.value.selectedFile[_thisIndexNumber].image = reader.result;
  };
};

// 画像データ等取得
const onImageUploaded = (e: Event) => {
  if (e.target instanceof HTMLInputElement) {
    if (e.target.files) {
      const image = e.target.files[0];
      const _thisId = e.target.id;
      let _thisIndex = _thisId.split("__");
      let _thisIndexNumber = Number(_thisIndex[1]);

      createImage(image, _thisIndexNumber);
    }
  }
};

//中略
<div class="file-item-add-button">
  <input
    type="file"
    :id="n.fileId"
    :name="n.fileId"
    class="file_input"
    @change="onImageUploaded"
   />
<Button :data-fileId="n.fileId" :disabled="false">
 ファイルを選択
</Button>
</div>

プレビューボタンを押して画像プレビューを出す

//画像のプレビュー
const previewImage = (e: Event) => {
  e.preventDefault();
  const _this = e.currentTarget as HTMLElement;
  const dataFileId = _this.getAttribute("data-fileId");
  if (dataFileId) {
    let _thisIndex = dataFileId.split("__");
    let _thisIndexNumber = Number(_thisIndex[1]);
    file.value.previewImage = file.value.selectedFile[_thisIndexNumber].image;
  }
  //画像が存在していたらモーダルあげる
  if (file.value.previewImage) {
    showModal.value = true;
  }
};

//中略
<Button
  :data-fileId="n.fileId"
  @click="previewImage"
  :disabled="!n.image"
>
 プレビュー
</Button>

//中略
  <!-- モーダル -->
  <transition name="modal">
    <Modal v-if="showModal" @close="showModal = false">
      <template v-slot:content>
        <div v-if="file.previewImage">
          <img :src="typeof file.previewImage === 'string' ? file.previewImage : ''" alt="" />
        </div>
      </template>
    </Modal>
  </transition>
  <!-- モーダル -->

モーダルのアニメーション

フワッと現れるモーダルアニメーションはvueの<transition>で実装しました。

下記サイトの方法を使わせていただきました。🙇
【Vue.js】モーダルの外側をクリックした時に閉じる方法 | ゆうやの雑記ブログ
https://yuyauver98.me/vue-modal-selfclose/

添付ファイルのセルを1つ追加する

file.value.selectedFileに新しい配列を追加します。
file.value.selectedFileの配列が増えると、v-forでループしている<div class="file-item">も増えていきます。

//添付ファイルのセルを1つ追加
const addFileIttem = (e: Event) => {
  e.preventDefault();

  let fileValue = file.value;
  let selectedFile = fileValue.selectedFile;

  const selectedFileLength = selectedFile.length;
  //selectedFileにpush
  const fileArray = {
    id: selectedFileLength,
    fileId: "file_input__" + selectedFileLength,
    name: fileValue.defaultName,
    image: null,
  };
  selectedFile.push(fileArray);
  file.value.selectedFile = selectedFile;
};

//中略
<div class="file">
 <div class="file-item" v-for="n in file.selectedFile" :key="n.id">
   // file.selectedFileの分だけループ
  </div>
</div>


//中略
//5つまで増やすことができ、セルが5つになるとボタンは非活性になる
 <Button
   @click="addFileIttem"
   :disabled="5 <= file.selectedFile.length"
 >
   ファイルの追加
</Button>

参考サイト

【Vue.js 3.2】<script setup> 構文がすごくすごい
https://zenn.dev/azukiazusa/articles/676d88675e4e74

【Vue.js】ref と reactive どっちを使う?
https://zenn.dev/azukiazusa/articles/ref-vs-article

【Vue.js】モーダルの外側をクリックした時に閉じる方法 | ゆうやの雑記ブログ
https://yuyauver98.me/vue-modal-selfclose/

FileReader.readAsDataURL() – Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/FileReader/readAsDataURL