TypeScriptでFireStoreのMap型をmapでループする

こんにちは。てぃろです。

今回は最近私がハマったTypeScriptの実装についてのメモ書きです。

やりたかったことは、FireStoreに格納されているMap型のデータをReact+TypeScriptのフロントで取得してループさせて表示すること、でした。

これだけ聞くと、簡単にできそうなのですが、そううまくいかずハマってしまいました…。

結論:Mapを表すInterfaceを自作してしまう

重要な部分を抜粋すると、以下の通りに扱うことができます。

// FireStoreのMapのデータ構造そのままのインターフェースを用意する
interface Users {
  [key: string] : {
    name: string;
    age: string; 
  }
}

// FireStoreでデータをそのまま受け取る(リアルタイム更新の場合を例に)
db.collection('sampledatas').onSnapshot((snapshot) => {
  snapshot.forEach((doc) => {
    const data = doc.data();
    users: data.users;  // データ構造を加工せずにそのまま受け取る
});


// JSXの中では、Objec.keysでkeyによってループし、valueの取得にはusers[key]という形を使う
Object.keys(users).map((key) => (
    <p>{users[key].name}</p>
    <p>{users[key].age}</p>
))

本来はそれぞれ別々の適切な箇所に書くべきものを抜粋していますので、これをコピペされる際には適切な箇所にそれぞれコピペされることをお勧めします。

ハマったポイントは、FireStoreのMap型をTypeScriptのMap型で受け取ろうとしてしまったこと

FireStoreのMap型のデータ構造は、JavaScript上で受け取るとObejct型として受け取ることができるようです。コンソールで表示するとJSONのように表示されていたりします。

そこでまだまだTypeScript初心者の私はMap型というので、Map型で受け取ればいいのかと思ったのですがうまくいきませんでした。

何がうまくいかなかったのかというと、Object.keysのループ内で、users[key]とするとusers.getを使えとエラーが出てしまい、しかたなくusers.getを使ってみると、usersにはgetなんてファンクションはない!とエラーが出て…。

「どうやってvalueを取得すればいいの!?」となっていたのです(泣)

結局悪かったのは、先にも書いた通り受け取るときの型であり、Map型では受け取れなかったんですね。最初は以下のように定義してました。

interface User {
    name: string;
    age: string; 
}

users: Map[string, User]

このMap型では期待通りに受け取れずkeyの型がanyになっていたようです。だからusers[key]users.getも使えなかったということのようです。

コンソール表示すると一瞬valueの値が表示されていたので、余計混乱していたところもあります。

おそらくJavaScrip的には受け取れていたんでしょうけど、TypeScript的にアウトだったのだと思います。

最後に

TypeScriptの型はバグ混入の危険性を減らしてくれて非常に便利ですが、たまにこういう難解な部分があるのが厳しいですね。ただのJavaScriptに戻るつもりもないので、根気よく付き合っていきたいと思います。