React + TypeScript でファイルアップロード機能を実装する

はじめに

React + TypeScript なフロントエンドからファイルをアップロードする機能を実装する機会があったので、簡単に備忘録として残しておく。

前提としては、フロントエンドから REST 形式で配信されているファイルアップロード用の外部 API に対してリクエストを送るという形を想定している。

API の実装については深くは触れずにフロントエンドでは「こう実装してみた!」という内容を多めに書いていく。

実装方法の検討

WebAPI でファイルをアップロードする方法アレコレ - Qiita
WebAPI を開発していると、ファイルを扱いたい場面に出くわすこともあると思います。 ただ、いざ WebAPI にファイルアップロードの仕組みを入れようとすると、いまいちしっくり来る方法がわからず、悩んだりするのではないでしょうか。 今回は実サービスの例を踏まえつつ、どの...
WebAPI でファイルをアップロードする方法アレコレ - Qiita favicon qiita.com
WebAPI でファイルをアップロードする方法アレコレ - Qiita

API の実装方法については、こちらの記事を読んで今回は multipart/form-data で用意した。少し脱線するが、画像の話が出てくるだけで、通常の RESTful な API では考慮しないようなことがどんどん出てくるので非常に面白い。API を実装する際に考慮することのような話も今後書けるように勉強しておきたい。

本題に戻るが、フロントエンドから API をコールする際に "Content-Type": "multipart/form-data" を付与してあげれば良さそう。

API クライアントには慣れている axios を使用した。

UI を作る

まず、こんな感じで適当にフォームを作ってファイルを選択できるような UI を作る。

App.tsx
export const App = (): JSX.Element => {
  return (
    <div className="App">
      <div className="App-form">
        <input name="file" type="file" accept="image/*" />
        <input type="button" disabled={!file} value="送信" />
      </div>
    </div>
  )
}

マークアップは雑だが、とりあえず下記のような見た目ができあがる。

React UI

送信するファイルの拡張子を制限したい、あるいは緩和したい場合は、accept="image/*" の部分を変更すると良い。

ファイルを保持できるようにする

ファイルを選択したら、そのデータをリクエストに含めたいので、ファイルを選択した際の処理を書いていく。

App.tsx
import React, { useState } from "react"

export const App = (): JSX.Element => {
  const [file, setFile] = useState<File | null>(null)

  const onChangeFile = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files
    if (files && files[0]) {
      setFile(files[0])
    }
  }

  return (
    <div className="App">
      <div className="App-form">
        <input
          name="file"
          type="file"
          accept="image/*"
          onChange={onChangeFile}
        />
        <input type="button" disabled={!file} value="送信" />
      </div>
    </div>
  )
}
  • ファイルを選択する度に Change メソッドが呼ばれ、onChangeFile が発火する。
  • 予め useState フックを使ってファイルのデータを file という state 変数に保持し、onChangeFilesetFile を呼び出し state を更新する。

送信ボタンを押下してリクエスト送信する

続いて送信ボタン押下した際の処理を書いていく。

App.tsx
import React, { useState } from "react"
import axios, { AxiosError } from "axios"

export const App = (): JSX.Element => {
  const [file, setFile] = useState<File | null>(null)

  const onChangeFile = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files
    if (files && files[0]) {
      setFile(files[0])
    }
  }

  const onClickSubmit = async () => {
    if (!file) {
      return
    }
    const formData = new FormData()
    formData.append("file", file)

    await axios.post(`${apiUrl}/api/upload`, formData)
      .then((res) => {
        console.log(res.data)
      })
      .catch((e: AxiosError) => {
        console.error(e)
      })
    }
  }

  return (
    <div className="App">
      <div className="App-form">
        <input
          name="file"
          type="file"
          accept="image/*"
          onChange={onChangeFile}
        />
        <input type="button" disabled={!file} value="送信" onClick={onClickSubmit} />
      </div>
    </div>
  )
}
  • 送信ボタンを押下すると onClickSubmit が発火。
  • FormData オブジェクトを生成し、state 変数 file が存在すれば append でフィールドに追加する。
  • axios で POST リクエストを送信する。その際、リクエスト Body に FormData を含めることで HTTP ヘッダに "Content-Type"": multipart/form-data; boundary=----xxxx が付与されているのが下記の画像からも分かる。

Request

(エラーになっているのは localhost:5000 を起動していないだけなので無関係)

まとめ

今回は、React + TypeScript でファイルのアップロード機能を実装してみた。おそらく API 側で受け取れるようになっているはず。

ファイルの扱い方にも触れることができて勉強となった。あとはファイル選択部分の UI をカスタマイズしてみたり、受け取る側の API を実装したりしてみたい。

参考

ハンズオンNode.js
Node.jsの入門書。対象読者は、フロントエンド開発の知識はあってもサーバサイド開発は知らないエンジニアや、他言語の経験はあってもNode.jsは触ったことがないプログラマー。 本書ではターミナルのプロンプトにコマンドを入力してその反応を確認したり、簡単なスクリプトをNode.js環境で実行したりしながら、Node.jsプログラミングの基本からWebアプリケーションの開発、テスト、デプロイまでをハンズオン形式で学びます。また、コードの背景にある設計思想や、プログラムの挙動の仕組みについてもしっかり掘り下げます。 本書のゴールは、読者がNode.jsの全体像を掴み、業務レベルでのアプリケーション開発に対応可能な知識を身につけることです。
ハンズオンNode.js  favicon amazon.co.jp
ハンズオンNode.js
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで Software Design plus
こちらの書籍は 2023/08/30 紙版の4刷に対応するため更新をおこないました。 (概要) TypeScriptは、JavaScriptに静的型付けの機能を加えたオープンソースのプログラミング言語です。本書では、根幹となるJavaScriptの仕様・機能とともに、TypeScript独自の仕様・機能を解説します。TypeScriptの基礎知識はこれ一冊だけで学べます。 静的型付き言語は世にいくつもありますが、TypeScriptの型システムは他に類を見ない高い表現力を持っています。本書の読者が、型の有効性を理解しTypeScriptらしいコードを書けるようになるために、本書では、プログラムの安全性を高める基本的な型の扱い方から、TypeScriptの「高い表現力」の源となっているリテラル型・ユニオン型・keyof 型の扱い方まで幅広く取り上げます。また、わかりにくい機能や型安全を脅かす危険な機能についてもごまかさず、歴史的経緯や目的・用途を踏まえたうえで最善の扱い方を説明します。 章ごとに力試し問題を用意しており、理解の度合いを確認しながら学習を進められます。 (こんな方におすすめ) ・TypeScriptの初学者 ・JavaScriptの知識はないが、TypeScriptを学び始めたい人 (目次) 第1章 イントロダクション   1.1 TypeScriptとは   1.2 TypeScriptとJavaScriptとの関係   1.3 TypeScriptの開発環境 第2章 基本的な文法・基本的な型   2.1 文と式   2.2 変数の宣言と使用   2.3 プリミティブ型   2.4 演算子   2.5 基本的な制御構文   2.6 力試し 第3章 オブジェクトの基本とオブジェクトの型   3.1 オブジェクトとは   3.2 オブジェクトの型   3.3 部分型関係   3.4 型引数を持つ型   3.5 配列   3.6 分割代入   3.7 その他の組み込みオブジェクト   3.8 力試し 第4章 TypeScriptの関数   4.1 関数の作り方   4.2 関数の型   4.3 関数型の部分型関係   4.4 ジェネリクス   4.5 変数スコープと関数   4.6 力試し 第5章 TypeScriptのクラス   5.1 クラスの宣言と使用   5.2 クラスの型   5.3 クラスの継承   5.4 this   5.5 例外処理   5.6 力試し 第6章 高度な型   6.1 ユニオン型とインターセクション型   6.2 リテラル型   6.3 型の絞り込み   6.4 keyof型・lookup型   6.5 asによる型アサーション   6.6 any型とunknown型   6.7 さらに高度な型   6.8 力試し 第7章 TypeScriptのモジュールシステム   7.1 import宣言とexport宣言   7.2 Node.jsのモジュールシステム   7.3 DefinitelyTypedと@types   7.4 力試し 第8章 非同期処理   8.1 非同期処理とは   8.2 コールバックによる非同期処理の扱い   8.3 Promiseを使う   8.4 async/await構文   8.5 力試し 第9章 TypeScriptのコンパイラオプション   9.1 tsconfig.jsonによるコンパイラオプションの設定   9.2 チェックの厳しさに関わるオプション 続きを読む
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで Software Design plus  favicon amazon.co.jp
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで Software Design plus

関連記事