React+Next.js+TypeScript+firebaseで画像アップローダを作った

前回、React+Next.js+firebaseで画像アップローダを作りました。
React+Next.js+firebaseで画像アップローダを作った - あおいろメモ

今回はこれをTypeScriptで書いてみます。

こんな超シンプルなアップローダができます

画像アップローダ

前回同様、firebaseの認証等は、「react.js&next.js超入門 第2版」を参考に、 components/fire.jsに保存しました。

index.tsx

// このサイト参考に画像アップロードサイト作成
// How to do image upload with firebase in react.
// https://dev.to/itnext/how-to-do-image-upload-with-firebase-in-react-cpj
// これはJSで係れているのでTSに直した

// 他参考書籍・サイト

// 『firebase公式ドキュメント』
// https://firebase.google.com/docs/storage/web/start?hl=ja

//『react.js&next.js超入門 第2版』
//https://www.shuwasystem.co.jp/book/9784798063980.html

// 『はじめてつくるReactアプリ with TypeScript』
// https://www.amazon.co.jp/dp/B094Z1R281

import {useState} from 'react';
import firebase from "firebase"

import Head from 'next/head'
import "../../components/fire"

const storage= firebase.storage()

const Index=()=> {
  
  return (
    <div>
      <Head>
        <title>TS Test</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" crossOrigin="anonymous"></link>
      </Head>
      <Upload />
    </div>
  )
}

// 型定義
type AllInputsType = {
    imgUrl: string
}

const Upload = ()=>{
    const allInputs = {imgUrl: ''}
    const [imageAsFile, setImageAsFile] = useState<any>('')
    const [imageAsUrl, setImageAsUrl] = useState<AllInputsType>(allInputs)

    // Eventの型は一回間違えてからエラーが出たところに
    // VSCode上でオーバーレイしてヒントを出すとわかる。
    // ただanyでいいかも。
    const handleImageAsFile=(e:React.ChangeEvent<HTMLInputElement>)=>{
        console.log(e)
        const image = e.target.files[0]
        setImageAsFile(imageFile => (image))
    }

    const handleFireBaseUpload=(e:React.FormEvent<HTMLFormElement>)=>{
        e.preventDefault()
        console.log('start of upload')
        // async magic goes here...
        if(imageAsFile === '' ||  imageAsFile === undefined) {
          console.error(`not an image, the image file is a ${typeof(imageAsFile)}`)
        } else {
          const uploadTask = storage.ref(`/images/${imageAsFile.name}`).put(imageAsFile)
          //initiates the firebase side uploading 
          //https://firebase.google.com/docs/storage/web/upload-files?hl=ja#monitor_upload_progress
          //このドキュメントだけ呼んだだけだと分かりにくいが、
          //uploadTask.onの第1引数に'state_changed'を指定すると
          //第2、第3、第4引数に渡した関数で
          // アップロード状況を管理することができる。
          uploadTask.on('state_changed', 
          (snapShot) => {
            //takes a snap shot of the process as it is happening
            console.log(snapShot)
          }, (err) => {
            //catches the errors
            console.log(err)
          }, () => {
            // gets the functions from storage refences the image storage in firebase by the children
            // gets the download url then sets the image from firebase as the value for the imgUrl key:
            storage.ref('images').child(imageAsFile.name).getDownloadURL()
            .then(fireBaseUrl => {
              //オブジェクトのスプレッド構文による展開と、値の更新
              //{imgUrl: 古いURL}を{imgUrl: 新しいURL}に更新している。
              //https://qiita.com/FumioNonaka/items/58358a29850afd7a0f37
              setImageAsUrl(prevObject => ({...prevObject, imgUrl: fireBaseUrl}))
            })
          })
        }
      }
    
    return <div>
        <form className="form-group" onSubmit={handleFireBaseUpload}>
        <input className="form-control-file mb-1"
            // allows you to reach into your file directory and upload image to the browser
            type="file"
            onChange={handleImageAsFile}
            />
        <button className="btn btn-primary">アップロード</button>
        </form>
        <img src={imageAsUrl.imgUrl} alt="image tag" />
    </div>
};

export default Index

Eventの型について

onChangeonSubmitに関数を指定しますが、const 関数=(e:型)=>{}の形で、この関数に渡されるe(Event)の型を定義しなければなりません。これについては

型定義をわざと間違える

このようにわざとstringのような間違えた型をつけます。そうすると

エラーにオーバーレイするとどんな型を指定すればいいか出てくる

JSX内のonChangeに赤波が引かれ、そのエラーにマウスをオーバーレイすると、型ヒントが出てきます。今回の場合Eventの正しい型はReact.ChangeEvent<HTMLInputElement>でした。(Next.jsの場合暗黙にReactimportされているので、名前空間としてReact.を前につけないといけないと思われる。)

ただ、
TypeScript の型定義に凝りすぎじゃね? - Neo's World
に述べられているようにあまり型にこだわりすぎず、anyに逃げた方がいいと思われます。

『はじめてつくるReactアプリ with TypeScript』でよくあった、自分で定義したオブジェクトの型を明示的に示すくらいがちょうどいいのかもしれないです。(勉強不足)