最近気づいたasync/awaitの使いどころ

最近気づいたこと

これを気づいたきっかけについてメモしておく。

FirebaseのCloudFunction

最近この記事を参考に認証コードログインをFireabaseのCloudFunctionで実装しようとしているのだが
Firebase Authenticationで認証コードログインを実現する - Qiita

この中で、CloudFunction(サーバ側)のJSプログラムでCall functionというものを使っている。
Call functions from your app  |  Firebase Documentation

このCall functionでデータを返すには、Promiseで返さなくてはならない。つまり、Promiseを返すような関数を書かなくてはならない。

さらに今回は認証コードログインのために、この関数の中でもcustome claimをユーザーに付与するsetCustomUserClaimsを待たなくてはならない。

つまり中でPromiseを待って、外にPromiseを返す関数を書かなくてはならない。このようなときにasync/awaitはぴったりだと気付いた。

Promiseを使った場合

const VERIFY_CODE = "0112"

exports.authWithCode = functions.region('asia-northeast1').https.onCall( (data, context) => {
  // 設定された認証コードと送られてきた認証コードが一致するか判定
  if(data.verifyCode === VERIFY_CODE) {
    return admin.auth().setCustomUserClaims(data.uid, {codeVerified: true})
      .then(
        ()=>{
          functions.logger.log("Success: ",data.uid," is authenticated !")
          return { status: 'ok', message: '認証しました。' }
        },
        ()=>{
          functions.logger.warn("Failed: ",data.uid," is failed to login ...")
          return { status: 'error', message: 'ログイン処理に失敗しました。' }
        }
      )
  }else{
    functions.logger.warn("Failed: ",data.verifyCode," is invalid code ...")
    return new Promise(
      (resolve)=>resolve({ status: 'error', message: '認証コードが正しくありません。' })
    )
  }

})

functions.https.onCallのコールバック関数の戻り値はPromiseでなければならない。 そして分かりにくい点が、長々と続いているPromiseオブジェクト.then(()=>{...})自体が返すべきPromiseオブジェクトなことである。

この最初のPromiseオブジェクトの結果に応じて結果(今回の場合成功すれば、{ status: 'ok', message: '認証しました。' }、失敗すれば{ status: 'error', message: 'ログイン処理に失敗しました。' }という内容)をresolveするPromiseを返すためには、最初のPromiseオブジェクトの前にreturnを書かなければならない。これはPromiseの仕組みを知っていれば当たり前の話なのだが、これでは処理の流れとコードを書くor読む順番が逆でなんともわかりにくい。

加えて、下の認証できなかった場合のコードではPromiseオブジェクトを生成してresolveを登録して、それを返している。これもめんどくさい処理である。

async/awaitであればこれらの処理がよりシンプルに書ける。

async/awaitを使った場合

const VERIFY_CODE = "0112"

exports.authWithCode = functions.region('asia-northeast1').https.onCall( async (data, context) => {
  // 設定された認証コードと送られてきた認証コードが一致するか判定
  if(data.verifyCode === VERIFY_CODE) {
    try{
      await admin.auth().setCustomUserClaims(data.uid, {codeVerified: true})
      functions.logger.log("Success: ",data.uid," is authenticated !")
      return { status: 'ok', message: '認証しました。' }
    }catch{
      functions.logger.warn("Failed: ",data.uid," is failed to login ...")
      return { status: 'error', message: 'ログイン処理に失敗しました。' }
    }
  }else{
    functions.logger.warn("Failed: ",data.verifyCode," is invalid code ...")
    return { status: 'error', message: '認証コードが正しくありません。' }
  }

})

コールバック関数はPromiseを返す必要があるのだが、これはasync関数にしてしまえば単純にreturnを書いただけで、それをresolveするPromiseが返されるので今回の用途に合っている。

また、関数の中でPromiseを待つ処理があるが、これはawaitで待って、次の処理を書けばいい。これもこちらの方がすっきりする。

そして無理やりにPromiseオブジェクトを作る必要もなくなった。

実際、この関数の行数も22行→16行とすっきりした。

promiseとasync/awaitの使い分けについて

いつでもasync/awaitを使った方がいいというわけではなく、どちらも使い分けだと考えられる。今回のような場合はasync/awaitの威力が発揮できたが、また別の場合は単純なPromiseのほうがいい場合もあると思われる。

母集団の期待値とt分布の関係

正規分布の期待値の分散の推定や検定に$t$分布がよく用いられるが、なぜ期待値の推定が、

\bar{X}-t_{+97.5}\sqrt{\frac{S^2}{n}}\lt \mu\lt \bar{X}-t_{-97.5}\sqrt{\frac{S^2}{n}}\tag{5}

のような式になるかの導出を毎回忘れるのでメモしておく。

定理と定義

正規分布$t$分布の関係を求めるのに必要な定理や定義を列挙する

分散の性質

定数倍の分散

確率変数$X$を定数倍した確率変数$kX$の分散は次のようになる。

V(kX)=k^2V(X)\tag{1}

確率変数の和の性質

確率変数$X$$Y$が独立な場合、その和も確率変数となり、その分散は次のようになる。

V(X+Y)=V(X)+V(Y)\tag{2}

正規分布の性質

$X$が期待値$\mu$、分散$\sigma ^ 2$正規分布に従うとき、$X\sim\mathcal{N}(\mu,\sigma ^ 2)$と書く。

正規分布の和の性質

2つの独立な確率変数$X\sim\mathcal{N}(\mu _ X,\sigma ^ 2 _ X)$$Y\sim\mathcal{N}(\mu _ Y,\sigma ^ 2 _ Y)$があるとき、それらの和の確率変数はは次のような正規分布に従う。(上で述べた分散の性質などを参考)

X+Y\sim\mathcal{N}(\mu_X+\mu_Y,\sigma^2_X+\sigma^2_Y)\tag{3}

標準正規分布

$X\sim\mathcal{N}(\mu,\sigma ^ 2)$である$X$から期待値を引いて、標準偏差で割って標準化した値は標準正規分布に従う。

\begin{aligned}Z&=\frac{X-\mu}{\sigma}\\Z&\sim\mathcal{N}(0,1)\end{aligned}

正規分布に従う複数の確率変数

同じ正規分布に従う確率変数が$X _ 1,\ldots,X _ n$と複数あり、それぞれ独立($X _ 1,\ldots,X _ N,i.i.d.{\sim} N(\mu,\sigma ^ 2)$)である場合、その合計$\sum _ {i=1} ^ n{X _ i}$と平均$\bar{X}$はそれぞれ次のような正規分布に従う。(上に述べた式($3$)、式($1$)などの性質を使えばわかる。)

\begin{aligned}&\sum_{i=1}^n{X_i}\sim\mathcal{N}(n\mu,n\sigma^2)\\&\bar{X}=\frac{1}{n}\sum_{i=1}^n{X_i}\sim\mathcal{N}(\mu,\frac{\sigma^2}{n})\end{aligned}

標準正規分布に従う複数の確率変数

また、標準正規分布に従う確率変数が$Z _ 1,\ldots,Z _ n$$n$個あり、それぞれ独立であるとする。あるいは、$X _ 1,\ldots,X _ n$を標準化した$Z _ 1,\ldots,Z _ n$としてもいい。平均$\bar{Z}$は次のような正規分布に従う。($\bar{Z}$$Z _ i\sim\mathcal{N}(0,1)$であることと、上に述べた数式を使えばいい。)

\bar{Z}\sim\mathcal{N}(0,\frac{1}{n})

一方、平方和は自由度$n$カイ二乗分布に従う。

Z_1^2+\cdots+Z_n^2=\sum_{i=1}^nZ_i^2\sim\chi^2_n

また、平均を引いて平方和をとった、偏差平方和は次のように自由度$n-1$カイ二乗分布に従う。またこれは$\bar{Z}\sim\mathcal{N}(0,1/n)$と独立である。

\sum_{i=1}^n(Z_i-\bar{Z})^2\sim\chi^2_{n-1}

なぜ偏差平方和が$\bar{Z}$と独立で、自由度が1減ったカイ二乗分布に従うかの証明については「久保川達也『現代数理統計学の基礎』」P87参照
https://www.amazon.co.jp/dp/4320111664/

$t$分布

確率変数$Z\sim\mathcal{N}(0,1)$$U\sim\chi ^ 2 _ m$が独立であるとき、次のような計算をした確率変数は自由度$m$$t$分布に従う。

T=\frac{Z}{\sqrt{U/m}}\sim t_m

母集団の期待値とt分布の関係

さて、ここまで準備して、母集団の期待値とt分布の関係が導出できる。

問題設定

正規分布$\mathcal{N}(\mu,\sigma ^ 2)$に従う母集団があるが、期待値$\mu$と分散$\sigma ^ 2$は未知だとする。このとき、少ない労力で、期待値$\mu$がどれくらいかについて知りたいとする。

方針

$t$分布になるように、確率変数$T$の分子と分母を作っていく。

分子を作る

まず、母集団から$n$個ランダムに標本を抽出し、$X _ 1,\ldots,X _ n$とすると、それらは独立に$\mathcal{N}(\mu,\sigma ^ 2)$に従う。そしてその平均$\bar{X}$は次のように$X _ 1,\ldots,X _ n$を標準化した$Z _ 1,\ldots,Z _ n$の平均$\bar{Z}$と次のような関係がある。

\begin{aligned}\bar{Z}&=\frac{1}{n}\sum_{i=1}^n{Z_i}\\&=\frac{1}{n}\sum_{i=1}^n\frac{{X_i}-\mu}{\sigma}\\&=\frac{\bar{X}-\mu}{\sigma}\end{aligned}

そして上で述べたように$\bar{Z}\sim\mathcal{N}(0,1/n)$なので、$\sqrt{n}\bar{Z}\sim\mathcal{N}(0,1)$となる。

よって$\sqrt{n}\bar{Z}$$T$の分子とすればいい。

分母を作る

$X _ 1,\ldots,X _ n$の不偏分散$S ^ 2$は次のようになる。

S^2=\frac{1}{n-1}\sum_{i=1}^n\left({X_i}-\bar{X}\right)^2

これを変形していくと、次のように$X _ i$を標準化した$Z _ i$で表せる。

\begin{aligned}S^2&=\frac{1}{n-1}\frac{\sigma^2}{\sigma^2}\sum_{i=1}^n\left({X_i}-\mu+\mu-\bar{X}\right)^2\\&=\frac{\sigma^2}{n-1}\sum_{i=1}^n\left(\frac{{X_i}-\mu}{\sigma}-\frac{\bar{X}-\mu}{\sigma}\right)^2\\&=\frac{\sigma^2}{n-1}\sum_{i=1}^n(Z_i-\bar{Z})^2\end{aligned}

よって上で述べたように、不偏分散を次のように変換したものは、$\bar{Z}$と独立で、自由度$n-1$$\chi ^ 2$分布に従う。これを$U$と置く。

\begin{aligned}U&=\frac{n-1}{\sigma^2}S^2\\&=\sum_{i=1}^n(Z_i-\bar{Z})^2\sim\chi^2_{n-1}\end{aligned}

よって$\sqrt{U/n-1}$を分母とすればいい。

$T$を導出する

以上で、分子と分母ができたので、$T$を導出する。計算したように、$\sqrt{n}\bar{Z}\sim\mathcal{N}(0,1)$$U\sim\chi ^ 2 _ {n-1}$は独立なので、次のように計算された$T$は自由度$n-1$$t$分布に従う。

\begin{aligned}T&=\frac{\sqrt{n}\bar{Z}}{\sqrt{U/n-1}}\\&=\frac{\sqrt{n}(\bar{X}-\mu)/\sigma}{\sqrt{(n-1)S^2/\left(\sigma^2(n-1)\right)}}\\&=\frac{(\bar{X}-\mu)}{\sqrt{S^2/n}}\sim t_{n-1}\tag{4}\end{aligned}

解釈

上で述べたように、$\bar{X}$$\mathcal{N}(\mu,\sigma ^ 2/n)$に従う。なのでこれを次のように標準化すれば、これは標準正規分布に従う。

\frac{\bar{X}-\mu}{\sqrt{\sigma^2/n}}=\sqrt{n}\bar{Z}\sim\mathcal{N}(0,1)

この標準化において、$\sigma ^ 2$は未知なので、それを不偏分散$S ^ 2$で置き換えた式、つまり式$4$正規分布に近い分布に従うと考えられる。この「正規分布に近い分布」が$t$分布である。

期待値がどれくらいか

$t$分布から、どのように期待値を推定するのだろうか。まず、上の$T\sim t _ {n-1}$の意味は

$\mathcal{N}(\mu,\sigma ^ 2)$に従う母集団からランダムに$n$個標本を抽出する。この標本を標本$1$とし、この標本$1$から式4で計算した$T$$T _ 1$とする。

このランダムな抽出を何回も繰り返し、標本$2$から$T _ 2$、標本$3$から$T _ 3$と計算していく。これら$T$の値、$T _ 1,T _ 2,\ldots$が従う確率分布が$t$分布である。」

という意味になる。

また、別の言い方をすれば、$t$分布の下側97.5%の点、上側97.5%の点をそれぞれ$t _ {-97.5}$$t _ {+97.5}$とすると、母集団から標本を抽出し、式$4$で計算した$T$$95$%の確率でこの間に入るとも言える。

t_{-97.5}\lt T\lt t_{+97.5}

この式を$\mu$がありそうな範囲を表すよう式変形すると、次のようになる。

\bar{X}-t_{+97.5}\sqrt{\frac{S^2}{n}}\lt \mu\lt \bar{X}-t_{-97.5}\sqrt{\frac{S^2}{n}}\tag{5}

このように、標本の平均$\bar{X}$と不偏分散$S ^ 2$から、母集団の期待値$\mu$がどれくらいになるかが推定できる。式の形から分かるように、抽出する標本の数$n$が多いほどこの推定は正確になる。

注意しなくてはならないのが期待値$\mu$は決して分布はしないということである。未知ではあるが、確実に決まった値を持っている。なので、式$5$は期待値$\mu$の分布を表すのではなく、標本$X _ 1,\ldots,X _ n$が抽出されたとき、これら限られた情報を用いて推定された期待値の推定である。情報が少なかったり、母集団の分散が大きければこの推定は大雑把になる。期待値自体は変化して分布はしないが、限られた情報から期待値を推定した値というのは情報の精度によってその範囲が決まってくるということである。

Next.js+FirebaseでレスポンシブにInstagram風に画像一覧表示

Insta風画像一覧

前回、

React+Next.js+TypeScript+firebaseで画像アップローダを作った - あおいろメモ

で画像アップローダを作ったので、今回は画像一覧表示を作った。

ストレージ

FireabaseのFirestoreを使った。

Firestore内のデータ一覧をlistAll()で取得する。

List files with Cloud Storage on Web  |  Firebase

  • 最近(2021/9/1)Firebaseがv8からv9に代わり、大きく記法が変わっているので注意
  • for文の中の中のPromiseがすべて終わってから処理したい処理は次の記事参照

Promiseの直列処理をループする(完了後に実行したい処理もある) - Qiita

バックエンド

React + Next.js

またnext/imageモジュールのImageタグを使った。通常のimgタグに比べ、いろいろ利点があるらしい

Next.js の Image コンポーネントで画像を表示する (next/image)|まくろぐ

また、アップロードファイルの拡張子をチェックして、画像以外取得しないようにした。

JavaScriptでアップロードするファイルの拡張子をチェックするサンプル

そしてInstagram風に画像を一覧表示するImageListコンポーネントを作成した。 このコンポーネントはurlの配列を受け取ってli要素を生成し、instagram風に並べる。

コンポーネントに配列を渡して内部で要素の配列を作成し、表示する方法については以下参照

React コンポーネントのプロパティで配列データを渡す|まくろぐ

レイアウト

まずは要素を並べるなら基本のflexを使う。下記記事参照。

今覚えたい!エンジニアのための CSS の基礎講座 〜Flexbox レイアウト編〜 | 株式会社ヌーラボ(Nulab inc.)

そして、要素を正方形にして、レスポンシブに対応させて並べるのは意外と複雑。 下の記事にも書いてある通り、いろいろな方法があるが、親要素の::before疑似要素についてpadding-top:100%にする方法が1番良い。

CSSで正方形を作る方法!レスポンシブに対応させるには? | 向壁虚造

そしてCSS Moduleでスタイルを適用した。

Next.js でコンポーネント単位の CSS を作成する (CSS Modules)|まくろぐ

また、scssを導入した

[SCSS]便利な&(アンパサンド)の使い方メモ - Qiita

ソース

Home.module.scss

// 正方形の作り方についてはhttps://kouhekikyozou.com/css_square
.image_list {
  display: flex;
  justify-content: flex-start;
  flex-wrap: wrap;
  width: 100%;
  max-width: 900px;

  li {
    position: relative; // 浮かせたimg要素の基準
    width: calc((100% - 20px) / 3); // 3カラムで表示させる
    margin-top: 10px;
    list-style-type: none;

    &::before {
      content: "";
      display: block;
      padding-top: 100%; 
    }

    &>div {
      position: absolute; // 浮かせる
      width: 90%; // 親要素の90%
      height: 90%; // 親要素の90%
      top: 5%;
      left: 5%;
    }
  }
}

index.tsx

import Head from 'next/head'
import styles from '../../styles/Home.module.scss'
import Image from 'next/image'

import fireabaseApp from "../../components/fire"
import { getStorage, ref, listAll, getDownloadURL } from 'firebase/storage'
import { useEffect, useState } from 'react'

const storage = getStorage(fireabaseApp)

// 拡張子チェック。ここを参照
// https://blog.ver001.com/javascript-get-extension/
function getExt(filename: string) {
  var pos = filename.lastIndexOf('.');
  if (pos === -1) return '';
  return filename.slice(pos + 1);
}

//許可する拡張子
var allow_exts = new Array('jpg', 'jpeg', 'png');

//ファイル名の拡張子が許可されているか確認する関数
function checkExt(filename: string) {
  //比較のため小文字にする
  var ext = getExt(filename).toLowerCase();
  //許可する拡張子の一覧(allow_exts)から対象の拡張子があるか確認する
  if (allow_exts.indexOf(ext) === -1) return false;
  return true;
}

// 配列データをコンポーネントに渡すやり方はここを参照
// https://maku.blog/p/av9mxak/
// idとurlを一緒にしたものの型
interface ImageInfo {
  id: number;
  url: string;
}

// ImageListに渡すpropsの型
interface ImageListProps {
  imlist: ImageInfo[]
}

const Index = () => {
  // useStateの初期値は必ず[]を指定。
  // useState()のままだと、undefinedが初期値となり、
  // 型が一致しないといわれてしまう。
  const [imlist, setImlist] = useState<ImageInfo[]>([])

  const store_url = () => {
    let temp_imlist: ImageInfo[] = [];
    let url = ""
    var listRef = ref(storage, "images")
    listAll(listRef)
      .then((res: any) => {
        let tasks: Array<any> = [];
        let count = 0;
        res.items.forEach((itemRef: any) => {
          // もし画像ファイルだったら、
          if (checkExt(itemRef.name)) {
            // 画像URLを取得、保存するPromiseを生成し、
            // そのPromiseをひとまとまりにする。
            tasks.push(getDownloadURL(itemRef)
              .then((fireBaseUrl: string) => {
                temp_imlist.push(
                  {
                    id: count,
                    url: fireBaseUrl
                  }
                )
                count++;
              })
            )
          }
        })
        // 複数のpromiseを待つ
        // https://qiita.com/saka212/items/ff61a6de9c3e19810c5d
        Promise.all(tasks).then(() => {
          setImlist(temp_imlist)
        })
      })
  }

  useEffect(() => {
    // ページを読み込んだ時のみで画像urlを取得
    store_url()
  }, [])

  return (
    <div>
      <Head>
        <title>TS Test</title>
      </Head>
      {/* ImageListコンポーネントに画像URLの配列を渡す */}
      <ImageList imlist={imlist} />
    </div>
  )
}

export default Index

// ImageListコンポーネントはurlの配列を受け取って
// li要素を生成し、instagram風に並べるコンポーネント
const ImageList = ({ imlist }: ImageListProps) => {
  // imlistプロパティの要素数が 0 であれば何も描画しない
  if (imlist.length == 0) return null;

  // imlistからitemを取り出し、item.idとitem.urlを組み込んで
  // li要素配列を生成する。
  const listItems = imlist.map((item: ImageInfo) =>
    <li key={item.id}>
      <div><Image src={item.url} objectFit="contain" layout="fill" alt="" /></div>
    </li>
  );

  return <div>
    <ul className={styles.image_list}>
      {listItems}
    </ul>
  </div>
}

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』でよくあった、自分で定義したオブジェクトの型を明示的に示すくらいがちょうどいいのかもしれないです。(勉強不足)

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

このサイト参考にアップロードサイト作成中
How to do image upload with firebase in react.

ただ、firebaseの認証等は、「react.js&next.js超入門 第2版」を参考に、 components/fire.jsに保存

//App.js
// このサイト参考にアップロードサイト作成中
// https://dev.to/itnext/how-to-do-image-upload-with-firebase-in-react-cpj
// いったん開発の間はstorageのルールの認証を切る
// いちいちログインするの時間がかかり非効率

// firebase公式ドキュメントも参考にする
// https://firebase.google.com/docs/storage/web/start?hl=ja

//またreact.js&next.js超入門 第2版も参考にした
//https://www.shuwasystem.co.jp/book/9784798063980.html

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

import Layout from "../../components/layout"
import "../../components/fire"

const auth = firebase.auth();
const provider = new firebase.auth.GoogleAuthProvider();
const storage= firebase.storage()

auth.signOut()

export default function Index() {
  const [user,setUser]=useState(null);
  const [message, setMessage] = useState('please login ...');

  // ログイン処理
  const login = () =>{
    console.log("login")
    auth.signInWithPopup(provider).then(
      result=>{
        setUser(result.user.displayName);
        setMessage('logined: '+result.user.displayName);
      },
      error=>{
        setUser('NONE');
        setMessage('not logined.')
      }
    )
  }

  // ログアウト処理
  const logout = () => {
    auth.signOut();
    setUser(null);
    setMessage('logout...')
  }

  // ログイン表示をクリックしたとき
  const doLogin = (e) => {
    if (auth.currentUser == null){
      login();
    } else {
      logout();
    }
  }

  return (
    <div>
      <Layout header="Next.js" title="Top page.">
      <div className="alert alert-primary text-center">
        <h6 className="text-right" onClick={doLogin}>LOGINED: {user}</h6>
        <h5 className="mb-4">{message}</h5>
      </div>
      <Upload />
      </Layout>
    </div>
  )
}

function Upload() {
  console.log("Upload 呼び出し")
  const allInputs = {imgUrl: ''}
  const [imageAsFile, setImageAsFile] = useState('')
  const [imageAsUrl, setImageAsUrl] = useState(allInputs)

  console.log(imageAsUrl)
  const handleImageAsFile = (e) => {
    const image = e.target.files[0]
    setImageAsFile(imageFile => (image))
  }

  const handleFireBaseUpload = e => {
    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>
}

無名関数とアロー関数のthisについて

要約

  • JSの普通の関数(名前あり関数)、無名関数内のthisは関数の呼び出し元(.(ドット)の前のオブジェクト)
  • 普通の関係、無名関数はbindを使えばthisを書き換えられる。
  • アロー関数のthisは宣言元のthisを示す。

本文

Java Script、jQueryについて引き続き勉強中なのでそれについてのメモ。

無名関数とアロー関数のthisについて分かったことがあったのでそれについてメモしておく。
本来、無名関数とアロー関数とthisの関数にjQueryAjaxは関係なのだが、jQueryAjaxを勉強中に気づいたことなので、jQueryAjaxを使ったコードになってしまっている。

やりたかったこと
jQuery リファレンス:jQuery.get
ここのサイトで勉強していて、jQueryAjaxのgetでクリックした要素の次の要素のデータを編集したいと思った。
しかし、jQuery.getはコールバック関数を呼び出すのだがそのコールバック関数内ではthisはまた別のものになってしまっている。

$(function() {
    $("#clickArea").click(function(){
        $.get("data/simple.txt", getFunc)
    });

    function  getFunc(myData, myStatus){
        $(this).next().html(myData);//ここでのthisは#clickArea要素ではない。
        $("body").append("myStatus = " + myStatus);
    };
});

上のコードのコメントで言っているように、コールバック関数内のthisは意図した#clickArea要素ではなく、関数呼び出し元のajaxオブジェクトになってしまっている。
どうにかして、コールバック関数に宣言元のthisを何らかの形で渡せればいいのだが...(伏線)

解決策.1: bindを使う

Javascriptのcall/apply関数のプロっぽい使い方 〜 JSおくのほそ道 #014 - Qiita

Javascriptのbind関数と部分適用 〜 JSおくのほそ道 #015 - Qiita

ここら辺を読んでcall,apply,bindを理解した。

bindを使えばthisを任意に書き換えられるらしい。つまりこうだ。

$(function() {
    $("#clickArea").click(function(){
        $.get("data/simple.txt", getFunc.bind(this))//コールバック関数を.bind(this)をつけて指定
    });

    function  getFunc(myData, myStatus){
        $(this).next().html(myData);//ここでのthisは#clickArea要素。
        $("body").append("myStatus = " + myStatus);
    };
});

これでうまく動く。
また、bindは無名関数の{}の最後に.bindとつければ、無名関数の場合も使える。

$(function() {
    $("#clickArea").click(function(){
        $.get("data/simple.txt", function  (myData, myStatus){
            $(this).next().html(myData);
            $("body").append("myStatus = " + myStatus);
        }.bind(this));//{}の最後の.bindとつければOK
    });
});

そして補足だが、bindは第二引数以降に追加の引数を指定することができる。追加の引数は元から渡される引数(ajaxのgetの場合は、data, textStatus, xhrとかの引数)の前に追加される。コードで示すと、

$(function() {
    $("#clickArea").click(function(){
        $.get("data/simple.txt", getFunc.bind(this,"Hello"))//引数"Hello"を追加
    });

    function  getFunc(greeting, myData, myStatus){//greetingで追加の引数を受け取る。
        $(this).next().html(myData);
        $("body").append("myStatus = " + myStatus);
        console.log(greeting);//Helloと表示される
    };
});

となる。

解決策2: アロー関数を使う
アロー関数内のthisは宣言元のthisをそのまま引き継ぐらしい。(アロー関数ではbindは使えないらしい)

$(function() {
    $("#clickArea").click(function(){
        $.get("data/simple.txt", (myData, myStatus)=>{
            $(this).next().html(myData);//ここでのthisは#clickArea要素。
            $("body").append("myStatus = " + myStatus);
        });
    });
});

bindを使わずにシンプルに書けるので、追加の引数を使いたい場合でなければこれを使えばいいかもしれない。

jQueryとJava Script比較

Java Script、jQuery勉強中なのでそれについてのメモ。

Java Scriptでは長く書かなければいけないことがjQueryの場合シンプルにかけることを知った。
div要素のbgcolorをconsole.logに表示するスクリプト
Java Scriptで書いた場合とjQueryで書いた場合を比較

Java Scriptバージョン

<!DOCTYPE html>
<html lang="ja">
<meta charset="UTF-8">
<title>JSテスト</title>

<script>
    // クリックしたdiv要素のbgcolorをconsole.logに表示する
    // JSクリプト
    document.addEventListener('DOMContentLoaded', function(){
        // div要素すべて取得。
        let elements = document.getElementsByTagName('div');
        let len = elements.length;
        for (let i = 0; i < len; i++){
            // クリックすると関数が実行されるよう属性を設定
            elements[i].addEventListener('click',function(){
                let style=window.getComputedStyle(this);
                let bgcolor=style.getPropertyValue("background-color");
                console.log(bgcolor);
            });
        }
    });
</script>

<style type="text/css">
div{
  width:50px;
  height:50px;
}

#redbox{
    background-color:#ff0000;
}

#bluebox{
    background-color:#0000ff;
}
</style>
</head>
<body>

<div id="redbox">
</div>

<div id="bluebox">
</div>

</body>
</html>

jQueryバージョン

<!DOCTYPE html>
<html lang="ja">
<meta charset="UTF-8">
<title>jQueryテスト</title>

<script type="text/javascript" src="jquery-3.6.0.min.js">
</script>
<script>
  // クリックしたdiv要素のbgcolorをconsole.logに表示する
  // jqueryスクリプト
$(function() {
  $("div").click(function() {
    var bgcolor = $(this).css("background-color"); 
    console.log(bgcolor);
  });
});
</script>

<style type="text/css">
div{
  width:50px;
  height:50px;
}

#redbox{
    background-color:#ff0000;
}

#bluebox{
    background-color:#0000ff;
}
</style>
</head>
<body>

<div id="redbox">
</div>

<div id="bluebox">
</div>

</body>
</html>