最近気づいたasync/awaitの使いどころ
最近気づいたこと
「わざわざasync関数作るのめんどくさいし、ネストが浅いなら、async/await使うより素直にthen書いた方がよくない?」とか思ってたんだけどネスト浅くてもAPIの要請で自分でPromise返すような関数書かなきゃならないときとか断然asyncで書いた方が書きやすいな・・・
— SolKul (@ICR3_5) November 19, 2021
これを気づいたきっかけについてメモしておく。
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$分布がよく用いられるが、なぜ期待値の推定が、
のような式になるかの導出を毎回忘れるのでメモしておく。
定理と定義
正規分布と$t$分布の関係を求めるのに必要な定理や定義を列挙する
分散の性質
定数倍の分散
確率変数$X$を定数倍した確率変数$kX$の分散は次のようになる。
確率変数の和の性質
確率変数$X$、$Y$が独立な場合、その和も確率変数となり、その分散は次のようになる。
正規分布の性質
$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\sim\mathcal{N}(\mu,\sigma ^ 2)$である$X$から期待値を引いて、標準偏差で割って標準化した値は標準正規分布に従う。
正規分布に従う複数の確率変数
同じ正規分布に従う確率変数が$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$)などの性質を使えばわかる。)
標準正規分布に従う複数の確率変数
また、標準正規分布に従う確率変数が$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)$であることと、上に述べた数式を使えばいい。)
一方、平方和は自由度$n$のカイ二乗分布に従う。
また、平均を引いて平方和をとった、偏差平方和は次のように自由度$n-1$のカイ二乗分布に従う。またこれは$\bar{Z}\sim\mathcal{N}(0,1/n)$と独立である。
なぜ偏差平方和が$\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分布の関係
さて、ここまで準備して、母集団の期待値と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}$と次のような関係がある。
そして上で述べたように$\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$は次のようになる。
これを変形していくと、次のように$X _ i$を標準化した$Z _ i$で表せる。
よって上で述べたように、不偏分散を次のように変換したものは、$\bar{Z}$と独立で、自由度$n-1$の$\chi ^ 2$分布に従う。これを$U$と置く。
よって$\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$分布に従う。
解釈
上で述べたように、$\bar{X}$は$\mathcal{N}(\mu,\sigma ^ 2/n)$に従う。なのでこれを次のように標準化すれば、これは標準正規分布に従う。
この標準化において、$\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$%の確率でこの間に入るとも言える。
この式を$\mu$がありそうな範囲を表すよう式変形すると、次のようになる。
このように、標本の平均$\bar{X}$と不偏分散$S ^ 2$から、母集団の期待値$\mu$がどれくらいになるかが推定できる。式の形から分かるように、抽出する標本の数$n$が多いほどこの推定は正確になる。
注意しなくてはならないのが期待値$\mu$は決して分布はしないということである。未知ではあるが、確実に決まった値を持っている。なので、式$5$は期待値$\mu$の分布を表すのではなく、標本$X _ 1,\ldots,X _ n$が抽出されたとき、これら限られた情報を用いて推定された期待値の推定である。情報が少なかったり、母集団の分散が大きければこの推定は大雑把になる。期待値自体は変化して分布はしないが、限られた情報から期待値を推定した値というのは情報の精度によってその範囲が決まってくるということである。
Next.js+FirebaseでレスポンシブにInstagram風に画像一覧表示
前回、
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の型について
onChange
やonSubmit
に関数を指定しますが、const 関数=(e:型)=>{}
の形で、この関数に渡されるe
(Event)の型を定義しなければなりません。これについては
このようにわざとstring
のような間違えた型をつけます。そうすると
JSX内のonChange
に赤波が引かれ、そのエラーにマウスをオーバーレイすると、型ヒントが出てきます。今回の場合Eventの正しい型はReact.ChangeEvent<HTMLInputElement>
でした。(Next.jsの場合暗黙にReact
がimport
されているので、名前空間として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
の関数にjQueryとAjaxは関係なのだが、jQueryとAjaxを勉強中に気づいたことなので、jQueryとAjaxを使ったコードになってしまっている。
やりたかったこと
jQuery リファレンス:jQuery.get
ここのサイトで勉強していて、jQueryのAjaxの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>