📕

File コンストラクタの存在を知り Storybook が喜んだ

2022/02/02に公開

JavaScript の File は、思うがまま自由に new してインスタンス化できるらしい。

const file = new File(["foo"], "bar.txt", {
  type: "text/plain",
});

出典:https://developer.mozilla.org/ja/docs/Web/API/File/File

いまさら何言ってるの? と呆れられる方もいらっしゃるかもしれませんが、私はつい最近になって初めて知りました。

File 関連でよくあるパターン

これまでの経験では、File を扱うのは以下の2パターンでした。

  1. <input type="file" /> でボタンの change イベントでファイルを受け取るとき
  2. Drag & Drop API の drop イベントでファイルを受け取るとき

アプリケーション開発で出くわしたパターン

React 開発で、file オブジェクトを受け取る React コンポーネントを作りました。

type Props = {
  file: File;
};

const FileViewer = ({ file }: Props) => {
  // ...
};

これを Storybook を使ってコンポーネント開発していたときに、File コンストラクタの存在を知らなかったばかりに、Git コミット履歴が2〜3回くらい紆余曲折してしまいました。

Fetch でも使える

Jest で使っていたテストデータを、Storybook でも使いまわして表示テストしてみます。

静的ファイルのディレクトリパスを渡すコマンドオプション -s は、カンマ区切りで複数指定可能でした。

yarn start-storybook -p 9009 -s public,src/libs/__tests__/testdata
# npm run start-storybook -p 9009 -s public,src/libs/__tests__/testdata

Storybook の内容は以下のような感じ。

import * as React from "react";

async function generateFile(filename: string): Promise<File> {
  const res = await fetch(filename);
  if (!res.ok) {
    throw new Error("invalid status code:" + res.status);
  }
  const blob = await res.blob(); // プレーンテキストなら res.text() でもOK
  return new File([blob], "羅生門.txt", { type: "text/plain" });
}

export const Demo = () => {
  const [file, setFile] = React.useState<File | null>(null);

  React.useEffect(() => {
    let unmounted = false;

    async function initializeFile() {
      const generatedFile = await generateFile("/rashomon.txt");
      if (!unmounted) {
        setFile(generatedFile);
      }
    }

    initializeFile();

    return () => {
      unmounted = true;
    };
  }, []);

  if (!file) {
    return null;
  }

  return <FileViewer file={file} />;
};

問題なく表示できました。

感想

慣れ親しんだ JavaScript の標準 API の中に、とりわけ新しいわけでもないのに見落としていた点があったのには驚きでした。

私と同じように、2〜3コミット紆余曲折してしまう方がいる可能性も米粒くらいあるかと思い、記事化してみました。

Discussion